diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..31a97b08
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,235 @@
+# Remove the line below if you want to inherit .editorconfig settings from higher directories
+root = true
+
+# C# files
+[*.cs]
+
+#### Core EditorConfig Options ####
+
+# Indentation and spacing
+indent_size = 4
+indent_style = space
+tab_width = 4
+
+# New line preferences
+end_of_line = crlf
+insert_final_newline = false
+max_line_length = 420
+
+#### .NET Coding Conventions ####
+
+# Organize usings
+dotnet_separate_import_directive_groups = false
+dotnet_sort_system_directives_first = true
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false
+dotnet_style_qualification_for_field = false
+dotnet_style_qualification_for_method = false
+dotnet_style_qualification_for_property = false
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true
+dotnet_style_predefined_type_for_member_access = true
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true
+dotnet_style_collection_initializer = true
+dotnet_style_explicit_tuple_names = true
+dotnet_style_namespace_match_folder = true
+dotnet_style_null_propagation = true
+dotnet_style_object_initializer = true
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true
+dotnet_style_prefer_compound_assignment = true
+dotnet_style_prefer_conditional_expression_over_assignment = true
+dotnet_style_prefer_conditional_expression_over_return = true
+dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
+dotnet_style_prefer_inferred_anonymous_type_member_names = true
+dotnet_style_prefer_inferred_tuple_names = true
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true
+dotnet_style_prefer_simplified_boolean_expressions = true
+dotnet_style_prefer_simplified_interpolation = true
+
+# Field preferences
+dotnet_style_readonly_field = true
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all
+
+# Suppression preferences
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+# New line preferences
+dotnet_style_allow_multiple_blank_lines_experimental = true
+dotnet_style_allow_statement_immediately_after_block_experimental = true
+
+#### C# Coding Conventions ####
+
+# var preferences
+csharp_style_var_elsewhere = false
+csharp_style_var_for_built_in_types = false
+csharp_style_var_when_type_is_apparent = false
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true
+csharp_style_expression_bodied_constructors = false
+csharp_style_expression_bodied_indexers = true
+csharp_style_expression_bodied_lambdas = true
+csharp_style_expression_bodied_local_functions = false
+csharp_style_expression_bodied_methods = false
+csharp_style_expression_bodied_operators = false
+csharp_style_expression_bodied_properties = true
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true
+csharp_style_pattern_matching_over_is_with_cast_check = true
+csharp_style_prefer_extended_property_pattern = true
+csharp_style_prefer_not_pattern = true
+csharp_style_prefer_pattern_matching = true
+csharp_style_prefer_switch_expression = true
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true
+
+# Modifier preferences
+csharp_prefer_static_local_function = true
+csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
+csharp_style_prefer_readonly_struct = true
+csharp_style_prefer_readonly_struct_member = true
+
+# Code-block preferences
+csharp_prefer_braces = true
+csharp_prefer_simple_using_statement = true
+csharp_style_namespace_declarations = file_scoped:warning
+csharp_style_prefer_method_group_conversion = true
+csharp_style_prefer_primary_constructors = true
+csharp_style_prefer_top_level_statements = true
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true
+csharp_style_deconstructed_variable_declaration = true
+csharp_style_implicit_object_creation_when_type_is_apparent = true
+csharp_style_inlined_variable_declaration = true
+csharp_style_prefer_index_operator = true
+csharp_style_prefer_local_over_anonymous_function = true
+csharp_style_prefer_null_check_over_type_check = true
+csharp_style_prefer_range_operator = true
+csharp_style_prefer_tuple_swap = true
+csharp_style_prefer_utf8_string_literals = true
+csharp_style_throw_expression = true
+csharp_style_unused_value_assignment_preference = discard_variable
+csharp_style_unused_value_expression_statement_preference = discard_variable
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace
+
+# New line preferences
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
+csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
+csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
+csharp_style_allow_embedded_statements_on_same_line_experimental = true
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+# ReSharper properties
+resharper_max_array_initializer_elements_on_line = 50
+resharper_max_initializer_elements_on_line = 1
+resharper_wrap_array_initializer_style = chop_if_long
diff --git a/.gitignore b/.gitignore
index a4fe18bd..0b2bbdce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,8 @@
*.user
*.userosscache
*.sln.docstates
+.idea
+.DS_Store
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 00000000..df5809be
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,32 @@
+
+
+ Elsa Workflows Community
+ 2023
+
+ https://github.com/elsa-workflows/elsa-core
+ https://github.com/elsa-workflows/elsa-core
+ git
+
+ latest
+ enable
+ enable
+
+ MIT
+ icon.png
+
+
+ https://v3.elsaworkflows.io/nuget-icon.png
+
+ true
+ Default
+ latest
+
+
+ true
+ snupkg
+ true
+
+
+ 3.3.2
+
+
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 00000000..93412d02
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,45 @@
+
+
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Elsa.Integrations.sln b/Elsa.Integrations.sln
new file mode 100644
index 00000000..1d4b0cbc
--- /dev/null
+++ b/Elsa.Integrations.sln
@@ -0,0 +1,49 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35527.113 d17.12
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{527248D6-B851-4C8D-8667-E2FB0A91DABF}"
+ ProjectSection(SolutionItems) = preProject
+ src\Directory.Build.props = src\Directory.Build.props
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution", "solution", "{DE39E491-5DDC-40F4-91D5-7F5FAF0E1884}"
+ ProjectSection(SolutionItems) = preProject
+ .gitignore = .gitignore
+ CONTRIBUTING.md = CONTRIBUTING.md
+ Directory.Build.props = Directory.Build.props
+ Directory.Packages.props = Directory.Packages.props
+ icon.png = icon.png
+ LICENSE = LICENSE
+ NuGet.Config = NuGet.Config
+ README.md = README.md
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Integrations.Slack", "src\Elsa.Integrations.Slack\Elsa.Integrations.Slack.csproj", "{9732E404-11B5-48DB-B5D9-97997F018830}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Integrations.Tests", "src\Elsa.Integrations.Tests\Elsa.Integrations.Tests.csproj", "{DD8CE0DF-4B4C-4E12-A89D-B1D543FAD185}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {9732E404-11B5-48DB-B5D9-97997F018830}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9732E404-11B5-48DB-B5D9-97997F018830}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9732E404-11B5-48DB-B5D9-97997F018830}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9732E404-11B5-48DB-B5D9-97997F018830}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DD8CE0DF-4B4C-4E12-A89D-B1D543FAD185}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DD8CE0DF-4B4C-4E12-A89D-B1D543FAD185}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DD8CE0DF-4B4C-4E12-A89D-B1D543FAD185}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DD8CE0DF-4B4C-4E12-A89D-B1D543FAD185}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {9732E404-11B5-48DB-B5D9-97997F018830} = {527248D6-B851-4C8D-8667-E2FB0A91DABF}
+ {DD8CE0DF-4B4C-4E12-A89D-B1D543FAD185} = {527248D6-B851-4C8D-8667-E2FB0A91DABF}
+ EndGlobalSection
+EndGlobal
diff --git a/NuGet.Config b/NuGet.Config
new file mode 100644
index 00000000..e5fb919d
--- /dev/null
+++ b/NuGet.Config
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/icon.png b/icon.png
new file mode 100644
index 00000000..47e1cade
Binary files /dev/null and b/icon.png differ
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
new file mode 100644
index 00000000..db78798a
--- /dev/null
+++ b/src/Directory.Build.props
@@ -0,0 +1,20 @@
+
+
+
+
+
+ net8.0;net9.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Channels/CreateChannel.cs b/src/Elsa.Integrations.Slack/Activities/Channels/CreateChannel.cs
new file mode 100644
index 00000000..b6bd4ec7
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Channels/CreateChannel.cs
@@ -0,0 +1,58 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Channels;
+
+///
+/// Creates a new channel in a Slack workspace.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Channels",
+ "Slack Channels",
+ "Creates a new channel in the workspace.",
+ DisplayName = "Create Channel")]
+[UsedImplicitly]
+public class CreateChannel : SlackActivity
+{
+ ///
+ /// The name of the channel to create.
+ ///
+ [Input(Description = "The name of the channel to create.")]
+ public Input ChannelName { get; set; } = default!;
+
+ ///
+ /// Whether to create the channel as private.
+ ///
+ [Input(Name = "Is Private", Description = "Whether to create the channel as private.")]
+ public Input IsPrivate { get; set; } = default!;
+
+ ///
+ /// The ID of the workspace.
+ ///
+ [Input(Name = "Team Id", Description = "The ID of the workspace.")]
+ public Input? TeamId { get; set; }
+
+ ///
+ /// The created channel information.
+ ///
+ [Output(Description = "The created channel information.")]
+ public Output Channel { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string channelName = context.Get(ChannelName)!;
+ bool isPrivate = context.Get(IsPrivate);
+ string? teamId = context.Get(TeamId);
+
+ ISlackApiClient client = GetClient(context);
+ Conversation channel = await client.Conversations.Create(channelName, isPrivate, teamId);
+
+ context.Set(Channel, channel);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Channels/GetChannel.cs b/src/Elsa.Integrations.Slack/Activities/Channels/GetChannel.cs
new file mode 100644
index 00000000..9b400504
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Channels/GetChannel.cs
@@ -0,0 +1,43 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Channels;
+
+///
+/// Retrieves information about a channel.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Channels",
+ "Slack Channels",
+ "Gets information about a channel.",
+ DisplayName = "Get Channel")]
+[UsedImplicitly]
+public class GetChannel : SlackActivity
+{
+ ///
+ /// The ID of the channel to get information about.
+ ///
+ [Input(Name = "Channel Id", Description = "The ID of the channel to get information about.")]
+ public Input ChannelId { get; set; } = default!;
+
+ ///
+ /// The channel information.
+ ///
+ [Output(Description = "The channel information.")]
+ public Output Channel { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string channelId = context.Get(ChannelId)!;
+
+ ISlackApiClient client = GetClient(context);
+ Conversation channel = await client.Conversations.Info(channelId);
+ context.Set(Channel, channel);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Channels/GetChannelMessage.cs b/src/Elsa.Integrations.Slack/Activities/Channels/GetChannelMessage.cs
new file mode 100644
index 00000000..8cdae7c9
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Channels/GetChannelMessage.cs
@@ -0,0 +1,64 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+using SlackNet.Events;
+using SlackNet.WebApi;
+
+namespace Elsa.Integrations.Slack.Activities.Channels;
+
+///
+/// Retrieves a message from a private channel.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Channels",
+ "Slack Channels",
+ "Gets a message from a private channel.",
+ DisplayName = "Get Private Channel Message")]
+[UsedImplicitly]
+public class GetChannelMessage : SlackActivity
+{
+ ///
+ /// The ID of the private channel containing the message.
+ ///
+ [Input(Name = "Channel Id", Description = "The ID of the private channel containing the message.")]
+ public Input ChannelId { get; set; } = default!;
+
+ ///
+ /// Timestamp of the message to retrieve.
+ ///
+ [Input(Name = "Message Timestamp", Description = "Timestamp of the message to retrieve.")]
+ public Input MessageTs { get; set; } = default!;
+
+ ///
+ /// The retrieved message.
+ ///
+ [Output(Description = "The retrieved message.")]
+ public Output Message { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string channelId = context.Get(ChannelId)!;
+ string messageTs = context.Get(MessageTs)!;
+
+ ISlackApiClient client = GetClient(context);
+ ConversationHistoryResponse history = await client.Conversations.History(
+ channelId,
+ latestTs: messageTs,
+ limit: 1,
+ inclusive: true);
+
+ MessageEvent? message = history.Messages.FirstOrDefault(m => m.Ts == messageTs);
+
+ if (message == null)
+ {
+ throw new InvalidOperationException($"Message with timestamp {messageTs} not found in channel {channelId}");
+ }
+
+ context.Set(Message, message);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Channels/KickUser.cs b/src/Elsa.Integrations.Slack/Activities/Channels/KickUser.cs
new file mode 100644
index 00000000..20dc7baa
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Channels/KickUser.cs
@@ -0,0 +1,43 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Channels;
+
+///
+/// Kicks (removes) a user from a channel.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Channels",
+ "Slack Channels",
+ "Removes a user from a channel.",
+ DisplayName = "Kick User From Channel")]
+[UsedImplicitly]
+public class KickUser : SlackActivity
+{
+ ///
+ /// The ID of the channel to remove the user from.
+ ///
+ [Input(Name = "Channel Id", Description = "The ID of the channel to remove the user from.")]
+ public Input ChannelId { get; set; } = default!;
+
+ ///
+ /// The ID of the user to remove from channel.
+ ///
+ [Input(Name = "User Id", Description = "The ID of the user to remove from channel.")]
+ public Input UserId { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string channelId = context.Get(ChannelId)!;
+ string userId = context.Get(UserId)!;
+
+ ISlackApiClient client = GetClient(context);
+ await client.Conversations.Kick(channelId, userId);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Channels/LeaveChannel.cs b/src/Elsa.Integrations.Slack/Activities/Channels/LeaveChannel.cs
new file mode 100644
index 00000000..bf3d3546
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Channels/LeaveChannel.cs
@@ -0,0 +1,36 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Channels;
+
+///
+/// Leaves a channel.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Channels",
+ "Slack Channels",
+ "Leaves a channel.",
+ DisplayName = "Leave Channel")]
+[UsedImplicitly]
+public class LeaveChannel : SlackActivity
+{
+ ///
+ /// The ID of the channel to leave.
+ ///
+ [Input(Name = "Channel Id", Description = "The ID of the channel to leave.")]
+ public Input ChannelId { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string channelId = context.Get(ChannelId)!;
+
+ ISlackApiClient client = GetClient(context);
+ await client.Conversations.Leave(channelId);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Channels/ListChannels.cs b/src/Elsa.Integrations.Slack/Activities/Channels/ListChannels.cs
new file mode 100644
index 00000000..7dcd25b5
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Channels/ListChannels.cs
@@ -0,0 +1,66 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+using SlackNet.WebApi;
+
+namespace Elsa.Integrations.Slack.Activities.Channels;
+
+///
+/// Lists all channels in a workspace.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Channels",
+ "Slack Channels",
+ "Lists all channels in the workspace.",
+ DisplayName = "List Channels")]
+[UsedImplicitly]
+public class ListChannels : SlackActivity
+{
+ ///
+ /// Set to true to exclude archived channels from the list.
+ ///
+ [Input(Name = "Exclude Archived", Description = "Set to true to exclude archived channels from the list.")]
+ public Input ExcludeArchived { get; set; } = default!;
+
+ ///
+ /// Number of channels to return per page.
+ ///
+ [Input(Description = "Number of channels to return per page.")]
+ public Input? Limit { get; set; }
+
+ ///
+ /// The cursor for the next page of results.
+ ///
+ [Input(Description = "Paginate through collections of data by setting the cursor parameter to a next_cursor attribute returned by a previous request's response_metadata.")]
+ public Input? Cursor { get; set; }
+
+ ///
+ /// The list of channels.
+ ///
+ [Output(Description = "The list of channels.")]
+ public Output> Channels { get; set; } = default!;
+
+ ///
+ /// The cursor for the next page of results.
+ ///
+ [Output(Name = "Next Cursor", Description = "The cursor for the next page of results.")]
+ public Output NextCursor { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ bool excludeArchived = context.Get(ExcludeArchived);
+ int limit = context.Get(Limit) ?? 100;
+ string? cursor = context.Get(Cursor);
+
+ ISlackApiClient client = GetClient(context);
+ ConversationListResponse response = await client.Conversations.List(excludeArchived, limit, cursor: cursor);
+
+ context.Set(Channels, response.Channels);
+ context.Set(NextCursor, response.ResponseMetadata?.NextCursor);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Channels/ListMembersInChannel.cs b/src/Elsa.Integrations.Slack/Activities/Channels/ListMembersInChannel.cs
new file mode 100644
index 00000000..006b6e31
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Channels/ListMembersInChannel.cs
@@ -0,0 +1,70 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+using SlackNet.WebApi;
+
+namespace Elsa.Integrations.Slack.Activities.Channels;
+
+///
+/// Lists members of a channel.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Channels",
+ "Slack Channels",
+ "Lists all members in a channel.",
+ DisplayName = "List Channel Members")]
+[UsedImplicitly]
+public class ListMembersInChannel : SlackActivity
+{
+ ///
+ /// The ID of the channel to list members from.
+ ///
+ [Input(Name = "Channel Id", Description = "The ID of the channel to list members from.")]
+ public Input ChannelId { get; set; } = default!;
+
+ ///
+ /// Number of members to return per page.
+ ///
+ [Input(Description = "Number of members to return per page.")]
+ public Input Limit { get; set; } = null!;
+
+ ///
+ /// Paginate through collections of data by setting the cursor parameter to a next_cursor attribute returned by a previous request's response_metadata.
+ ///
+ [Input(Description = "Paginate through collections of data by setting the cursor parameter to a next_cursor attribute returned by a previous request's response_metadata.")]
+ public Input Cursor { get; set; } = null!;
+
+ ///
+ /// The list of member IDs in the channel.
+ ///
+ [Output(Description = "The list of member IDs in the channel.")]
+ public Output> MemberIds { get; set; } = default!;
+
+ ///
+ /// The cursor for the next page of results.
+ ///
+ [Output(Name = "Next Cursor", Description = "The cursor for the next page of results.")]
+ public Output NextCursor { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string channelId = context.Get(ChannelId)!;
+ int limit = context.Get(Limit) ?? 100;
+ string? cursor = context.Get(Cursor);
+
+ ISlackApiClient client = GetClient(context);
+ ConversationMembersResponse response = await client.Conversations.Members(
+ channelId,
+ limit,
+ cursor,
+ context.CancellationToken);
+
+ context.Set(MemberIds, response.Members);
+ context.Set(NextCursor, response.ResponseMetadata?.NextCursor);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Channels/SetChannelPurpose.cs b/src/Elsa.Integrations.Slack/Activities/Channels/SetChannelPurpose.cs
new file mode 100644
index 00000000..c0f37c29
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Channels/SetChannelPurpose.cs
@@ -0,0 +1,51 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Channels;
+
+///
+/// Sets the purpose of a channel.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Channels",
+ "Slack Channels",
+ "Sets the purpose for a channel.",
+ DisplayName = "Set Channel Purpose")]
+[UsedImplicitly]
+public class SetChannelPurpose : SlackActivity
+{
+ ///
+ /// The ID of the channel to set the purpose of.
+ ///
+ [Input(Name = "Channel Id", Description = "The ID of the channel to set the purpose of.")]
+ public Input ChannelId { get; set; } = default!;
+
+ ///
+ /// The new purpose.
+ ///
+ [Input(Description = "The new purpose.")]
+ public Input Purpose { get; set; } = default!;
+
+ ///
+ /// The updated purpose.
+ ///
+ [Output(Description = "The updated purpose.")]
+ public Output UpdatedPurpose { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string channelId = context.Get(ChannelId)!;
+ string purpose = context.Get(Purpose)!;
+
+ ISlackApiClient client = GetClient(context);
+ string updatedPurpose = await client.Conversations.SetPurpose(channelId, purpose);
+
+ context.Set(UpdatedPurpose, updatedPurpose);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Channels/SetChannelTopic.cs b/src/Elsa.Integrations.Slack/Activities/Channels/SetChannelTopic.cs
new file mode 100644
index 00000000..fc6352aa
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Channels/SetChannelTopic.cs
@@ -0,0 +1,51 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Channels;
+
+///
+/// Sets the topic of a channel.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Channels",
+ "Slack Channels",
+ "Sets the topic for a channel.",
+ DisplayName = "Set Channel Topic")]
+[UsedImplicitly]
+public class SetChannelTopic : SlackActivity
+{
+ ///
+ /// The ID of the channel to set the topic of.
+ ///
+ [Input(Name = "Channel Id", Description = "The ID of the channel to set the topic of.")]
+ public Input ChannelId { get; set; } = default!;
+
+ ///
+ /// The new topic.
+ ///
+ [Input(Description = "The new topic.")]
+ public Input Topic { get; set; } = default!;
+
+ ///
+ /// The updated topic.
+ ///
+ [Output(Description = "The updated topic.")]
+ public Output UpdatedTopic { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string channelId = context.Get(ChannelId)!;
+ string topic = context.Get(Topic)!;
+
+ ISlackApiClient client = GetClient(context);
+ string updatedTopic = await client.Conversations.SetTopic(channelId, topic);
+
+ context.Set(UpdatedTopic, updatedTopic);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Events/SlackEventActivity.cs b/src/Elsa.Integrations.Slack/Activities/Events/SlackEventActivity.cs
new file mode 100644
index 00000000..dac8eaf6
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Events/SlackEventActivity.cs
@@ -0,0 +1,13 @@
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+
+namespace Elsa.Integrations.Slack.Activities.Events;
+
+///
+/// Base class for Slack event watching activities.
+///
+public abstract class SlackEventActivity : SlackActivity
+{
+ [Input(Name = "Bot User Id", Description = "The ID of the bot user to filter out self-messages.")]
+ public Input BotUserId { get; set; } = default!;
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Events/WatchDirectMessages.cs b/src/Elsa.Integrations.Slack/Activities/Events/WatchDirectMessages.cs
new file mode 100644
index 00000000..73d53eba
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Events/WatchDirectMessages.cs
@@ -0,0 +1,28 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet.WebApi;
+
+namespace Elsa.Integrations.Slack.Activities.Events;
+
+///
+/// Triggers when a direct message is received.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Events",
+ "Slack Events",
+ "Triggers when a direct message is received.",
+ DisplayName = "Watch Direct Messages")]
+[UsedImplicitly]
+public class WatchDirectMessages : SlackEventActivity
+{
+ [Output(Description = "The received message.")]
+ public Output ReceivedMessage { get; set; } = default!;
+
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ // Implementation depends on Slack's Events API and WebSocket support
+ throw new NotImplementedException("Event subscription requires WebSocket implementation.");
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Events/WatchFiles.cs b/src/Elsa.Integrations.Slack/Activities/Events/WatchFiles.cs
new file mode 100644
index 00000000..073877e0
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Events/WatchFiles.cs
@@ -0,0 +1,34 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using File = SlackNet.File;
+
+namespace Elsa.Integrations.Slack.Activities.Events;
+
+///
+/// Triggers when a new file is added.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Events",
+ "Slack Events",
+ "Triggers when a new file is added.",
+ DisplayName = "Watch Files")]
+[UsedImplicitly]
+public class WatchFiles : SlackEventActivity
+{
+ ///
+ /// The added file.
+ ///
+ [Output(Description = "The added file.")]
+ public Output AddedFile { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ // Implementation depends on Slack's Events API and WebSocket support
+ throw new NotImplementedException("Event subscription requires WebSocket implementation.");
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Events/WatchMultipartyDirectMessages.cs b/src/Elsa.Integrations.Slack/Activities/Events/WatchMultipartyDirectMessages.cs
new file mode 100644
index 00000000..89eafccc
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Events/WatchMultipartyDirectMessages.cs
@@ -0,0 +1,34 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet.WebApi;
+
+namespace Elsa.Integrations.Slack.Activities.Events;
+
+///
+/// Triggers when a message is added to a multiparty direct message.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Events",
+ "Slack Events",
+ "Triggers when a message is added to a multiparty direct message.",
+ DisplayName = "Watch Multiparty Direct Messages")]
+[UsedImplicitly]
+public class WatchMultipartyDirectMessages : SlackEventActivity
+{
+ ///
+ /// The received message.
+ ///
+ [Output(Description = "The received message.")]
+ public Output ReceivedMessage { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ // Implementation depends on Slack's Events API and WebSocket support
+ throw new NotImplementedException("Event subscription requires WebSocket implementation.");
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Events/WatchNewEvents.cs b/src/Elsa.Integrations.Slack/Activities/Events/WatchNewEvents.cs
new file mode 100644
index 00000000..53a256a6
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Events/WatchNewEvents.cs
@@ -0,0 +1,34 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using Elsa.Workflows.Runtime.Activities;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Slack.Activities.Events;
+
+///
+/// Triggers when any new event is created.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Events",
+ "Slack Events",
+ "Triggers when any new event is created.",
+ DisplayName = "Watch New Events")]
+[UsedImplicitly]
+public class WatchNewEvents : SlackEventActivity
+{
+ ///
+ /// The received event.
+ ///
+ [Output(Description = "The received event.")]
+ public Output ReceivedEvent { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ // Implementation depends on Slack's Events API and WebSocket support
+ throw new NotImplementedException("Event subscription requires WebSocket implementation.");
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Events/WatchPublicChannelMessages.cs b/src/Elsa.Integrations.Slack/Activities/Events/WatchPublicChannelMessages.cs
new file mode 100644
index 00000000..d05b32b9
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Events/WatchPublicChannelMessages.cs
@@ -0,0 +1,40 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet.WebApi;
+
+namespace Elsa.Integrations.Slack.Activities.Events;
+
+///
+/// Triggers when a message is added to a public channel.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Events",
+ "Slack Events",
+ "Triggers when a message is added to a public channel.",
+ DisplayName = "Watch Public Channel Messages")]
+[UsedImplicitly]
+public class WatchPublicChannelMessages : SlackEventActivity
+{
+ ///
+ /// The ID of the public channel to watch.
+ ///
+ [Input(Name = "Channel Id", Description = "The ID of the public channel to watch.")]
+ public Input ChannelId { get; set; } = default!;
+
+ ///
+ /// The received message.
+ ///
+ [Output(Description = "The received message.")]
+ public Output ReceivedMessage { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ // Implementation depends on Slack's Events API and WebSocket support
+ throw new NotImplementedException("Event subscription requires WebSocket implementation.");
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Events/WatchUsers.cs b/src/Elsa.Integrations.Slack/Activities/Events/WatchUsers.cs
new file mode 100644
index 00000000..82c54977
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Events/WatchUsers.cs
@@ -0,0 +1,34 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Events;
+
+///
+/// Triggers when a user is added or changed.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Events",
+ "Slack Events",
+ "Triggers when a user is added or changed.",
+ DisplayName = "Watch Users")]
+[UsedImplicitly]
+public class WatchUsers : SlackEventActivity
+{
+ ///
+ /// The user that was added or changed.
+ ///
+ [Output(Description = "The user that was added or changed.")]
+ public Output ChangedUser { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ // Implementation depends on Slack's Events API and WebSocket support
+ throw new NotImplementedException("Event subscription requires WebSocket implementation.");
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Files/UploadFile.cs b/src/Elsa.Integrations.Slack/Activities/Files/UploadFile.cs
new file mode 100644
index 00000000..501c2266
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Files/UploadFile.cs
@@ -0,0 +1,71 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+using SlackNet.WebApi;
+
+namespace Elsa.Integrations.Slack.Activities.Files;
+
+///
+/// Uploads a file to Slack.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Files",
+ "Slack Files",
+ "Uploads a file to Slack.",
+ DisplayName = "Upload File")]
+[UsedImplicitly]
+public class UploadFile : SlackActivity
+{
+ ///
+ /// The file to upload.
+ ///
+ [Input(Description = "The file to upload.")]
+ public Input File { get; set; } = default!;
+
+ ///
+ /// The channel ID where the file will be shared. If not specified the file will be private.
+ ///
+ [Input(Name = "Channel Id", Description = "Channel ID where the file will be shared. If not specified the file will be private.")]
+ public Input? ChannelId { get; set; }
+
+ ///
+ /// Provide another message's timestamp value to upload this file as a reply. Never use a reply's ts value; use its parent instead.
+ ///
+ [Input(Name = "Thread Timestamp", Description = "Provide another message's timestamp value to upload this file as a reply. Never use a reply's ts value; use its parent instead.")]
+ public Input? ThreadTs { get; set; }
+
+ ///
+ /// The message text introducing the file in specified channels.
+ ///
+ [Input(Name = "Initial Comment", Description = "The message text introducing the file in specified channels.")]
+ public Input? InitialComment { get; set; }
+
+ ///
+ /// The reference to the uploaded file.
+ ///
+ [Output(Name = "File Reference", Description = "The reference to the uploaded file.")]
+ public Output FileReference { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ FileUpload file = context.Get(File)!;
+ string? channelId = context.Get(ChannelId);
+ string? threadTs = context.Get(ThreadTs);
+ string? initialComment = context.Get(InitialComment);
+
+ ISlackApiClient client = GetClient(context);
+ ExternalFileReference fileReference = await client.Files.Upload(
+ file,
+ channelId,
+ threadTs,
+ initialComment,
+ context.CancellationToken);
+
+ context.Set(FileReference, fileReference);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Messages/CreateMessage.cs b/src/Elsa.Integrations.Slack/Activities/Messages/CreateMessage.cs
new file mode 100644
index 00000000..c1e6f4eb
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Messages/CreateMessage.cs
@@ -0,0 +1,75 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+using SlackNet.WebApi;
+
+namespace Elsa.Integrations.Slack.Activities.Messages;
+
+///
+/// Sends a new message to a Slack channel.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Messages",
+ "Slack Messages",
+ "Sends a new message to a Slack channel.",
+ DisplayName = "Send Message")]
+[UsedImplicitly]
+public class CreateMessage : SlackActivity
+{
+ ///
+ /// The channel to send the message to.
+ ///
+ [Input(Description = "The channel to send the message to.")]
+ public Input Channel { get; set; } = default!;
+
+ ///
+ /// The message text to send.
+ ///
+ [Input(Description = "The message text to send.")]
+ public Input Text { get; set; } = default!;
+
+ ///
+ /// Optional thread timestamp to reply to a thread.
+ ///
+ [Input(Description = "Optional thread timestamp to reply to a thread.")]
+ public Input ThreadTimestamp { get; set; } = null!;
+
+ ///
+ /// Whether to broadcast a reply to a thread to the channel.
+ ///
+ [Input(Description = "Whether to broadcast a reply to a thread to the channel.")]
+ public Input ReplyBroadcast { get; set; } = null!;
+
+ ///
+ /// The timestamp of the sent message.
+ ///
+ [Output(Description = "The timestamp of the sent message.")]
+ public Output MessageTimestamp { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string channel = context.Get(Channel)!;
+ string text = context.Get(Text)!;
+ string? threadTs = context.Get(ThreadTimestamp);
+ bool replyBroadcast = context.Get(ReplyBroadcast);
+
+ ISlackApiClient client = GetClient(context);
+
+ Message message = new()
+ {
+ Channel = channel,
+ Text = text,
+ ThreadTs = threadTs,
+ ReplyBroadcast = replyBroadcast
+ };
+
+ PostMessageResponse? response = await client.Chat.PostMessage(message);
+
+ context.Set(MessageTimestamp, response?.Ts);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Messages/DeleteMessage.cs b/src/Elsa.Integrations.Slack/Activities/Messages/DeleteMessage.cs
new file mode 100644
index 00000000..c04d67c9
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Messages/DeleteMessage.cs
@@ -0,0 +1,44 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Messages;
+
+///
+/// Deletes a message from a Slack channel.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Messages",
+ "Slack Messages",
+ "Deletes a message from a Slack channel.",
+ DisplayName = "Delete Message")]
+[UsedImplicitly]
+public class DeleteMessage : SlackActivity
+{
+ ///
+ /// The channel containing the message.
+ ///
+ [Input(Description = "The channel containing the message.")]
+ public Input Channel { get; set; } = default!;
+
+ ///
+ /// The timestamp of the message to delete.
+ ///
+ [Input(Description = "The timestamp of the message to delete.")]
+ public Input Timestamp { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string channel = context.Get(Channel)!;
+ string ts = context.Get(Timestamp)!;
+
+ ISlackApiClient client = GetClient(context);
+
+ await client.Chat.Delete(channel, ts);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Messages/PinMessage.cs b/src/Elsa.Integrations.Slack/Activities/Messages/PinMessage.cs
new file mode 100644
index 00000000..3e73d2a5
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Messages/PinMessage.cs
@@ -0,0 +1,44 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Messages;
+
+///
+/// Pins a message to a channel.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Messages",
+ "Slack Messages",
+ "Pins a message to a channel.",
+ DisplayName = "Pin Message")]
+[UsedImplicitly]
+public class PinMessage : SlackActivity
+{
+ ///
+ /// The channel containing the message.
+ ///
+ [Input(Description = "The channel containing the message.")]
+ public Input Channel { get; set; } = default!;
+
+ ///
+ /// The timestamp of the message to pin.
+ ///
+ [Input(Description = "The timestamp of the message to pin.")]
+ public Input Timestamp { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string channel = context.Get(Channel)!;
+ string ts = context.Get(Timestamp)!;
+
+ ISlackApiClient client = GetClient(context);
+
+ await client.Pins.AddMessage(channel, ts);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Messages/UnpinMessage.cs b/src/Elsa.Integrations.Slack/Activities/Messages/UnpinMessage.cs
new file mode 100644
index 00000000..adc91b12
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Messages/UnpinMessage.cs
@@ -0,0 +1,43 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Messages;
+
+///
+/// Unpins a message from a channel.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Messages",
+ "Slack Messages",
+ "Unpins a message from a channel.",
+ DisplayName = "Unpin Message")]
+[UsedImplicitly]
+public class UnpinMessage : SlackActivity
+{
+ ///
+ /// The channel containing the message.
+ ///
+ [Input(Description = "The channel containing the message.")]
+ public Input Channel { get; set; } = default!;
+
+ ///
+ /// The timestamp of the message to unpin.
+ ///
+ [Input(Description = "The timestamp of the message to unpin.")]
+ public Input Timestamp { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string channel = context.Get(Channel)!;
+ string ts = context.Get(Timestamp)!;
+
+ ISlackApiClient client = GetClient(context);
+ await client.Pins.RemoveMessage(channel, ts);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Messages/UpdateMessage.cs b/src/Elsa.Integrations.Slack/Activities/Messages/UpdateMessage.cs
new file mode 100644
index 00000000..e0e11aef
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Messages/UpdateMessage.cs
@@ -0,0 +1,59 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+using SlackNet.WebApi;
+
+namespace Elsa.Integrations.Slack.Activities.Messages;
+
+///
+/// Updates an existing message in a Slack channel.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Messages",
+ "Slack Messages",
+ "Updates an existing message in a Slack channel.",
+ DisplayName = "Update Message")]
+[UsedImplicitly]
+public class UpdateMessage : SlackActivity
+{
+ ///
+ /// The channel ID containing the message.
+ ///
+ [Input(Description = "The channel ID containing the message.")]
+ public Input ChannelId { get; set; } = default!;
+
+ ///
+ /// The timestamp of the message to update.
+ ///
+ [Input(Description = "The timestamp of the message to update.")]
+ public Input Timestamp { get; set; } = default!;
+
+ ///
+ /// The new text for the message.
+ ///
+ [Input(Description = "The new text for the message.")]
+ public Input Text { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string channelId = context.Get(ChannelId)!;
+ string ts = context.Get(Timestamp)!;
+ string text = context.Get(Text)!;
+
+ ISlackApiClient client = GetClient(context);
+
+ MessageUpdate message = new()
+ {
+ ChannelId = channelId,
+ Text = text,
+ Ts = ts
+ };
+
+ await client.Chat.Update(message);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Reactions/AddReaction.cs b/src/Elsa.Integrations.Slack/Activities/Reactions/AddReaction.cs
new file mode 100644
index 00000000..755d2aac
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Reactions/AddReaction.cs
@@ -0,0 +1,51 @@
+using Elsa.Integrations.Slack.Services;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using Elsa.Workflows;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Reactions;
+
+///
+/// Adds a reaction to a message.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Reactions",
+ "Slack Reactions",
+ "Adds a reaction to a message.",
+ DisplayName = "Add Reaction")]
+[UsedImplicitly]
+public class AddReaction : SlackActivity
+{
+ ///
+ /// The name of the emoji to react with.
+ ///
+ [Input(Description = "The name of the emoji to react with.")]
+ public Input Emoji { get; set; } = default!;
+
+ ///
+ /// The channel containing the message.
+ ///
+ [Input(Description = "The channel containing the message.")]
+ public Input Channel { get; set; } = default!;
+
+ ///
+ /// The timestamp of the message to react to.
+ ///
+ [Input(Description = "The timestamp of the message to react to.")]
+ public Input Timestamp { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string emoji = context.Get(Emoji)!;
+ string channel = context.Get(Channel)!;
+ string timestamp = context.Get(Timestamp)!;
+
+ ISlackApiClient client = GetClient(context);
+ await client.Reactions.AddToMessage(emoji, channel, timestamp);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Reactions/ListReactions.cs b/src/Elsa.Integrations.Slack/Activities/Reactions/ListReactions.cs
new file mode 100644
index 00000000..797178f9
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Reactions/ListReactions.cs
@@ -0,0 +1,44 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+using SlackNet.WebApi;
+
+namespace Elsa.Integrations.Slack.Activities.Reactions;
+
+///
+/// Lists reactions made by a user.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Reactions",
+ "Slack Reactions",
+ "Lists reactions made by a user.",
+ DisplayName = "List Reactions")]
+[UsedImplicitly]
+public class ListReactions : SlackActivity
+{
+ ///
+ /// The user to list reactions for.
+ ///
+ [Input(Name = "User Id", Description = "The user to list reactions for.")]
+ public Input UserId { get; set; } = default!;
+
+ ///
+ /// The list of reactions made by the user.
+ ///
+ [Output(Name="User Reactions", Description="The list of reactions made by the user.")]
+ public Output> UserReactions { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string userId = context.Get(UserId)!;
+
+ ISlackApiClient client = GetClient(context);
+ ReactionItemListResponse? response = await client.Reactions.List(userId, full: true);
+ context.Set(UserReactions, response?.Items);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Reactions/RemoveReaction.cs b/src/Elsa.Integrations.Slack/Activities/Reactions/RemoveReaction.cs
new file mode 100644
index 00000000..1e3cd6e9
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Reactions/RemoveReaction.cs
@@ -0,0 +1,50 @@
+using Elsa.Integrations.Slack.Services;
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Reactions;
+
+///
+/// Removes a reaction from a message.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Reactions",
+ "Slack Reactions",
+ "Removes a reaction from a message.",
+ DisplayName = "Remove Reaction")]
+[UsedImplicitly]
+public class RemoveReaction : SlackActivity
+{
+ ///
+ /// The name of the emoji to remove.
+ ///
+ [Input(Description = "The name of the emoji to remove.")]
+ public Input Emoji { get; set; } = default!;
+
+ ///
+ /// The channel containing the message.
+ ///
+ [Input(Description = "The channel containing the message.")]
+ public Input Channel { get; set; } = default!;
+
+ ///
+ /// The timestamp of the message.
+ ///
+ [Input(Description = "The timestamp of the message.")]
+ public Input Timestamp { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string emoji = context.Get(Emoji)!;
+ string channel = context.Get(Channel)!;
+ string timestamp = context.Get(Timestamp)!;
+ ISlackApiClient client = GetClient(context);
+ await client.Reactions.RemoveFromMessage(emoji, channel, timestamp);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Reminders/CompleteReminder.cs b/src/Elsa.Integrations.Slack/Activities/Reminders/CompleteReminder.cs
new file mode 100644
index 00000000..0e78ee90
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Reminders/CompleteReminder.cs
@@ -0,0 +1,36 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Reminders;
+
+///
+/// Marks a reminder as complete.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Reminders",
+ "Slack Reminders",
+ "Marks a reminder as complete.",
+ DisplayName = "Complete Reminder")]
+[UsedImplicitly]
+public class CompleteReminder : SlackActivity
+{
+ ///
+ /// The ID of the reminder to mark as complete.
+ ///
+ [Input(Name = "Reminder Id", Description = "The ID of the reminder to mark as complete.")]
+ public Input ReminderId { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string reminderId = context.Get(ReminderId)!;
+
+ ISlackApiClient client = GetClient(context);
+ await client.Reminders.Complete(reminderId);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Reminders/CreateReminder.cs b/src/Elsa.Integrations.Slack/Activities/Reminders/CreateReminder.cs
new file mode 100644
index 00000000..e90aa4e3
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Reminders/CreateReminder.cs
@@ -0,0 +1,57 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Reminders;
+
+///
+/// Creates a reminder.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Reminders",
+ "Slack Reminders",
+ "Creates a new reminder.",
+ DisplayName = "Create Reminder")]
+[UsedImplicitly]
+public class CreateReminder : SlackActivity
+{
+ ///
+ /// The text of the reminder.
+ ///
+ [Input(Description = "The text of the reminder.")]
+ public Input Text { get; set; } = default!;
+
+ ///
+ /// When this reminder should happen: the Unix timestamp (up to 5 years from now), or a natural language description (e.g. 'in 15 minutes' or 'every Thursday at 3pm').
+ ///
+ [Input(Description = "When this reminder should happen: the Unix timestamp (up to 5 years from now), or a natural language description (e.g. 'in 15 minutes' or 'every Thursday at 3pm').")]
+ public Input Time { get; set; } = default!;
+
+ ///
+ /// The user who will receive this reminder. If not specified, defaults to the authed user.
+ ///
+ [Input(Name = "User Id", Description = "The user who will receive this reminder. If not specified, defaults to the authed user.")]
+ public Input? UserId { get; set; }
+
+ ///
+ /// The ID of the created reminder.
+ ///
+ [Output(Name = "Reminder Id", Description = "The ID of the created reminder.")]
+ public Output ReminderId { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string text = context.Get(Text)!;
+ string time = context.Get(Time)!;
+ string? userId = context.Get(UserId);
+
+ ISlackApiClient client = GetClient(context);
+ Reminder reminder = await client.Reminders.Add(text, time, userId);
+ context.Set(ReminderId, reminder.Id);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Reminders/DeleteReminder.cs b/src/Elsa.Integrations.Slack/Activities/Reminders/DeleteReminder.cs
new file mode 100644
index 00000000..28d312c7
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Reminders/DeleteReminder.cs
@@ -0,0 +1,36 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Reminders;
+
+///
+/// Deletes a reminder.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Reminders",
+ "Slack Reminders",
+ "Deletes a reminder.",
+ DisplayName = "Delete Reminder")]
+[UsedImplicitly]
+public class DeleteReminder : SlackActivity
+{
+ ///
+ /// The ID of the reminder to delete.
+ ///
+ [Input(Name = "Reminder Id", Description = "The ID of the reminder to delete.")]
+ public Input ReminderId { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string reminderId = context.Get(ReminderId)!;
+
+ ISlackApiClient client = GetClient(context);
+ await client.Reminders.Delete(reminderId, CancellationToken.None);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Reminders/GetReminder.cs b/src/Elsa.Integrations.Slack/Activities/Reminders/GetReminder.cs
new file mode 100644
index 00000000..66951b14
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Reminders/GetReminder.cs
@@ -0,0 +1,43 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Reminders;
+
+///
+/// Retrieves information about a reminder.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Reminders",
+ "Slack Reminders",
+ "Gets information about a specific reminder.",
+ DisplayName = "Get Reminder")]
+[UsedImplicitly]
+public class GetReminder : SlackActivity
+{
+ ///
+ /// The ID of the reminder to get information about.
+ ///
+ [Input(Name = "Reminder Id", Description = "The ID of the reminder to get information about.")]
+ public Input ReminderId { get; set; } = default!;
+
+ ///
+ /// The reminder information.
+ ///
+ [Output(Name = "Reminder Info", Description = "The reminder information.")]
+ public Output ReminderInfo { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string reminderId = context.Get(ReminderId)!;
+
+ ISlackApiClient client = GetClient(context);
+ Reminder reminder = await client.Reminders.Info(reminderId);
+ context.Set(ReminderInfo, reminder);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Reminders/ListReminders.cs b/src/Elsa.Integrations.Slack/Activities/Reminders/ListReminders.cs
new file mode 100644
index 00000000..25343dc7
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Reminders/ListReminders.cs
@@ -0,0 +1,43 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Reminders;
+
+///
+/// Lists all reminders created by or for a given user.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Reminders",
+ "Slack Reminders",
+ "Lists all reminders for a user.",
+ DisplayName = "List Reminders")]
+[UsedImplicitly]
+public class ListReminders : SlackActivity
+{
+ ///
+ /// The user to list reminders for. If not specified, defaults to the authed user.
+ ///
+ [Input(Name = "User Id", Description = "The user to list reminders for. If not specified, defaults to the authed user.")]
+ public Input? UserId { get; set; }
+
+ ///
+ /// The list of reminders.
+ ///
+ [Output(Description = "The list of reminders.")]
+ public Output> Reminders { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string? userId = context.Get(UserId);
+
+ ISlackApiClient client = GetClient(context);
+ IReadOnlyList? response = await client.Reminders.List();
+ context.Set(Reminders, response);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Search/SearchForMessage.cs b/src/Elsa.Integrations.Slack/Activities/Search/SearchForMessage.cs
new file mode 100644
index 00000000..3c4ed646
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Search/SearchForMessage.cs
@@ -0,0 +1,58 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+using SlackNet.WebApi;
+
+namespace Elsa.Integrations.Slack.Activities.Search;
+
+///
+/// Searches for messages matching a query.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Search",
+ "Slack Search",
+ "Searches for messages matching a query.",
+ DisplayName = "Search Messages")]
+[UsedImplicitly]
+public class SearchForMessage : SlackActivity
+{
+ ///
+ /// The search query.
+ ///
+ [Input(Description = "Search query - can include modifiers like 'in:#channel', 'from:@user', etc.")]
+ public Input Query { get; set; } = default!;
+
+ ///
+ /// Number of results to return per page.
+ ///
+ [Input(Description = "Number of results to return per page.")]
+ public Input Count { get; set; } = null!;
+
+ ///
+ /// Page number of results to return.
+ ///
+ [Input(Description = "Page number of results to return.")]
+ public Input Page { get; set; } = null!;
+
+ ///
+ /// The search results.
+ ///
+ [Output(Description = "The search results.")]
+ public Output> Results { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string query = context.Get(Query)!;
+ int count = context.Get(Count) ?? 100;
+ int page = context.Get(Page) ?? 1;
+
+ ISlackApiClient client = GetClient(context);
+ MessageSearchResponse results = await client.Search.Messages(query, count: count, page: page);
+ context.Set(Results, results.Messages);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/SlackActivity.cs b/src/Elsa.Integrations.Slack/Activities/SlackActivity.cs
new file mode 100644
index 00000000..f66289b0
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/SlackActivity.cs
@@ -0,0 +1,31 @@
+using Elsa.Integrations.Slack.Services;
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities;
+
+///
+/// Generic base class inherited by all Slack activities.
+///
+public abstract class SlackActivity : Activity
+{
+ ///
+ /// The Slack API token.
+ ///
+ [Input(Description = "The Slack API token.")]
+ public Input Token { get; set; } = default!;
+
+ ///
+ /// Gets the Slack API client.
+ ///
+ /// The current context to get the client.
+ /// The Slack API client.
+ protected ISlackApiClient GetClient(ActivityExecutionContext context)
+ {
+ SlackClientFactory slackClientFactory = context.GetRequiredService();
+ string token = context.Get(Token)!;
+ return slackClientFactory.GetClient(token);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/SlackTriggerActivity.cs b/src/Elsa.Integrations.Slack/Activities/SlackTriggerActivity.cs
new file mode 100644
index 00000000..55030000
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/SlackTriggerActivity.cs
@@ -0,0 +1,28 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+
+namespace Elsa.Integrations.Slack.Activities;
+
+///
+/// Base class for Slack event trigger activities.
+///
+public abstract class SlackTriggerActivity : SlackActivity, ITrigger
+{
+ ///
+ /// The ID of the bot user to filter out self-messages.
+ ///
+ [Input(Name = "Bot User Id", Description = "The ID of the bot user to filter out self-messages.")]
+ public Input BotUserId { get; set; } = default!;
+
+ ///
+ /// The ID of the channel to listen for messages in.
+ ///
+ public abstract string GetTriggerType();
+
+ ///
+ /// Returns the payloads to index.
+ ///
+ /// The trigger indexing context.
+ public abstract ValueTask> GetTriggerPayloadsAsync(TriggerIndexingContext context);
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Stars/RemoveSavedItem.cs b/src/Elsa.Integrations.Slack/Activities/Stars/RemoveSavedItem.cs
new file mode 100644
index 00000000..c547987f
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Stars/RemoveSavedItem.cs
@@ -0,0 +1,43 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Stars;
+
+///
+/// Removes an item from saved items.
+///
+[Activity(
+ "Elsa.Integrations.Slack.SavedItems",
+ "Slack Saved Items",
+ "Removes a saved item.",
+ DisplayName = "Remove Saved Item")]
+[UsedImplicitly]
+public class RemoveSavedItem : SlackActivity
+{
+ ///
+ /// Channel where the saved item is located.
+ ///
+ [Input(Name = "Channel Id", Description = "Channel where the saved item is located.")]
+ public Input ChannelId { get; set; } = default!;
+
+ ///
+ /// Timestamp of the message to remove from saved items.
+ ///
+ [Input(Description = "Timestamp of the message to remove from saved items.")]
+ public Input Timestamp { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string channelId = context.Get(ChannelId)!;
+ string timestamp = context.Get(Timestamp)!;
+
+ ISlackApiClient client = GetClient(context);
+ await client.Pins.RemoveMessage(channelId, timestamp);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Stars/SaveItem.cs b/src/Elsa.Integrations.Slack/Activities/Stars/SaveItem.cs
new file mode 100644
index 00000000..12e7a0d8
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Stars/SaveItem.cs
@@ -0,0 +1,43 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Stars;
+
+///
+/// Adds an item to saved items.
+///
+[Activity(
+ "Elsa.Integrations.Slack.SavedItems",
+ "Slack Saved Items",
+ "Saves an item for later reference.",
+ DisplayName = "Save Item")]
+[UsedImplicitly]
+public class SaveItem : SlackActivity
+{
+ ///
+ /// Channel where the item is located.
+ ///
+ [Input(Name = "Channel Id", Description = "Channel where the item is located.")]
+ public Input ChannelId { get; set; } = default!;
+
+ ///
+ /// Timestamp of the message to save.
+ ///
+ [Input(Description = "Timestamp of the message to save.")]
+ public Input Timestamp { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string channelId = context.Get(ChannelId)!;
+ string timestamp = context.Get(Timestamp)!;
+
+ ISlackApiClient client = GetClient(context);
+ await client.Pins.AddMessage(channelId, timestamp);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Users/GetUser.cs b/src/Elsa.Integrations.Slack/Activities/Users/GetUser.cs
new file mode 100644
index 00000000..de86676b
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Users/GetUser.cs
@@ -0,0 +1,43 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Users;
+
+///
+/// Retrieves details about a member of a workspace.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Users",
+ "Slack Users",
+ "Retrieves details about a member of a workspace.",
+ DisplayName = "Get User")]
+[UsedImplicitly]
+public class GetUser : SlackActivity
+{
+ ///
+ /// The ID of the user to get information about.
+ ///
+ [Input(Description = "The ID of the user to get information about.")]
+ public Input UserId { get; set; } = default!;
+
+ ///
+ /// The user information.
+ ///
+ [Output(Description = "The user information.")]
+ public Output UserInfo { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string userId = context.Get(UserId)!;
+
+ ISlackApiClient client = GetClient(context);
+ User user = await client.Users.Info(userId);
+ context.Set(UserInfo, user);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Users/ListUsers.cs b/src/Elsa.Integrations.Slack/Activities/Users/ListUsers.cs
new file mode 100644
index 00000000..d185b052
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Users/ListUsers.cs
@@ -0,0 +1,38 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+using SlackNet.WebApi;
+
+namespace Elsa.Integrations.Slack.Activities.Users;
+
+///
+/// Lists all users in a workspace.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Users",
+ "Slack Users",
+ "Lists all users in a workspace.",
+ DisplayName = "List Users")]
+[UsedImplicitly]
+public class ListUsers : SlackActivity
+{
+ ///
+ /// The list of users in the workspace.
+ ///
+ [Output(Description = "The list of users in the workspace.")]
+ public Output> Users { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string token = context.Get(Token)!;
+
+ ISlackApiClient client = GetClient(context);
+ UserListResponse users = await client.Users.List();
+ context.Set(Users, users.Members);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Users/SearchForUser.cs b/src/Elsa.Integrations.Slack/Activities/Users/SearchForUser.cs
new file mode 100644
index 00000000..7ad736c2
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Users/SearchForUser.cs
@@ -0,0 +1,43 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Users;
+
+///
+/// Searches for a user by email address.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Users",
+ "Slack Users",
+ "Searches for a user by email address.",
+ DisplayName = "Search User By Email")]
+[UsedImplicitly]
+public class SearchForUser : SlackActivity
+{
+ ///
+ /// The email address to search for.
+ ///
+ [Input(Description = "The email address to search for.")]
+ public Input Email { get; set; } = default!;
+
+ ///
+ /// The found user information.
+ ///
+ [Output(Description = "The found user information.")]
+ public Output FoundUser { get; set; } = default!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string email = context.Get(Email)!;
+
+ ISlackApiClient client = GetClient(context);
+ User user = await client.Users.LookupByEmail(email);
+ context.Set(FoundUser, user);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Activities/Users/SetStatus.cs b/src/Elsa.Integrations.Slack/Activities/Users/SetStatus.cs
new file mode 100644
index 00000000..384f3c8f
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Activities/Users/SetStatus.cs
@@ -0,0 +1,56 @@
+using Elsa.Workflows;
+using Elsa.Workflows.Attributes;
+using Elsa.Workflows.Models;
+using JetBrains.Annotations;
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Activities.Users;
+
+///
+/// Sets a user's status.
+///
+[Activity(
+ "Elsa.Integrations.Slack.Users",
+ "Slack Users",
+ "Sets a user's status.",
+ DisplayName = "Set Status")]
+[UsedImplicitly]
+public class SetStatus: SlackActivity
+{
+ ///
+ /// The status text to set.
+ ///
+ [Input(Description = "The status text to set.")]
+ public Input StatusText { get; set; } = default!;
+
+ ///
+ /// The emoji to use for the status.
+ ///
+ [Input(Description = "The emoji to use for the status.")]
+ public Input StatusEmoji { get; set; } = default!;
+
+ ///
+ /// Optional Unix timestamp for when the status should expire.
+ ///
+ [Input(Description = "Optional Unix timestamp for when the status should expire.")]
+ public Input StatusExpiration { get; set; } = null!;
+
+ ///
+ /// Executes the activity.
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ string statusText = context.Get(StatusText)!;
+ string statusEmoji = context.Get(StatusEmoji)!;
+ long? statusExpiration = context.Get(StatusExpiration);
+
+ ISlackApiClient client = GetClient(context);
+
+ await client.UserProfile.Set(new UserProfile
+ {
+ StatusText = statusText,
+ StatusEmoji = statusEmoji,
+ StatusExpiration = statusExpiration ?? 0
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Elsa.Integrations.Slack.csproj b/src/Elsa.Integrations.Slack/Elsa.Integrations.Slack.csproj
new file mode 100644
index 00000000..8b9cd49c
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Elsa.Integrations.Slack.csproj
@@ -0,0 +1,16 @@
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Elsa.Integrations.Slack/Features/SlackFeature.cs b/src/Elsa.Integrations.Slack/Features/SlackFeature.cs
new file mode 100644
index 00000000..b66c18a3
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Features/SlackFeature.cs
@@ -0,0 +1,19 @@
+using Elsa.Features.Abstractions;
+using Elsa.Features.Services;
+using Elsa.Integrations.Slack.Services;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Elsa.Integrations.Slack.Features;
+
+///
+/// Represents a feature for setting up Slack integration within the Elsa framework.
+///
+public class SlackFeature(IModule module) : FeatureBase(module)
+{
+ ///
+ /// Applies the feature to the specified service collection.
+ ///
+ public override void Apply() =>
+ Services
+ .AddSingleton();
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/FodyWeavers.xml b/src/Elsa.Integrations.Slack/FodyWeavers.xml
new file mode 100644
index 00000000..00e1d9a1
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Slack/Services/SlackClientFactory.cs b/src/Elsa.Integrations.Slack/Services/SlackClientFactory.cs
new file mode 100644
index 00000000..f7deb8a6
--- /dev/null
+++ b/src/Elsa.Integrations.Slack/Services/SlackClientFactory.cs
@@ -0,0 +1,37 @@
+using SlackNet;
+
+namespace Elsa.Integrations.Slack.Services;
+
+///
+/// Factory for creating Slack API clients.
+///
+public class SlackClientFactory
+{
+ private readonly SemaphoreSlim _semaphore = new(1, 1);
+ private readonly Dictionary _slackClients = new();
+
+ ///
+ /// Gets a Slack API client for the specified token.
+ ///
+ public ISlackApiClient GetClient(string token)
+ {
+ if (_slackClients.TryGetValue(token, out ISlackApiClient? client))
+ return client;
+
+ try
+ {
+ _semaphore.Wait();
+
+ if (_slackClients.TryGetValue(token, out client))
+ return client;
+
+ SlackApiClient newClient = new(token);
+ _slackClients[token] = newClient;
+ return newClient;
+ }
+ finally
+ {
+ _semaphore.Release();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Tests/Elsa.Integrations.Tests.csproj b/src/Elsa.Integrations.Tests/Elsa.Integrations.Tests.csproj
new file mode 100644
index 00000000..e2b9a772
--- /dev/null
+++ b/src/Elsa.Integrations.Tests/Elsa.Integrations.Tests.csproj
@@ -0,0 +1,29 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Elsa.Integrations.Tests/FodyWeavers.xml b/src/Elsa.Integrations.Tests/FodyWeavers.xml
new file mode 100644
index 00000000..00e1d9a1
--- /dev/null
+++ b/src/Elsa.Integrations.Tests/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Tests/GlobalUsings.cs b/src/Elsa.Integrations.Tests/GlobalUsings.cs
new file mode 100644
index 00000000..3334ee7f
--- /dev/null
+++ b/src/Elsa.Integrations.Tests/GlobalUsings.cs
@@ -0,0 +1 @@
+global using FluentAssertions;
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Tests/Slack/Activities/Channels/CreateChannelTests.cs b/src/Elsa.Integrations.Tests/Slack/Activities/Channels/CreateChannelTests.cs
new file mode 100644
index 00000000..92939b7c
--- /dev/null
+++ b/src/Elsa.Integrations.Tests/Slack/Activities/Channels/CreateChannelTests.cs
@@ -0,0 +1,15 @@
+using Elsa.Integrations.Slack.Activities.Channels;
+
+namespace Elsa.Integrations.Tests.Slack.Activities.Channels;
+
+///
+/// Contains tests for the activity.
+///
+public class CreateChannelTests
+{
+ ///
+ /// Tests the activity by creating a new channel in a Slack workspace.
+ ///
+ [Fact]
+ public void ExecuteAsync() => throw new NotImplementedException();
+}
\ No newline at end of file