diff --git a/packages/nodes-base/credentials/TwistOAuth2Api.credentials.ts b/packages/nodes-base/credentials/TwistOAuth2Api.credentials.ts
index 850a53dabc..3c6b820791 100644
--- a/packages/nodes-base/credentials/TwistOAuth2Api.credentials.ts
+++ b/packages/nodes-base/credentials/TwistOAuth2Api.credentials.ts
@@ -6,7 +6,9 @@ import {
const scopes = [
'attachments:write',
'channels:remove',
+ 'comments:remove',
'messages:remove',
+ 'threads:remove',
'workspaces:read',
];
diff --git a/packages/nodes-base/nodes/Twist/ChannelDescription.ts b/packages/nodes-base/nodes/Twist/ChannelDescription.ts
index 6412125330..fd0e33a3eb 100644
--- a/packages/nodes-base/nodes/Twist/ChannelDescription.ts
+++ b/packages/nodes-base/nodes/Twist/ChannelDescription.ts
@@ -15,11 +15,21 @@ export const channelOperations = [
},
},
options: [
+ {
+ name: 'Archive',
+ value: 'archive',
+ description: 'Archive a channel',
+ },
{
name: 'Create',
value: 'create',
description: 'Initiates a public or private channel-based conversation',
},
+ {
+ name: 'Delete',
+ value: 'delete',
+ description: 'Delete a channel',
+ },
{
name: 'Get',
value: 'get',
@@ -30,6 +40,11 @@ export const channelOperations = [
value: 'getAll',
description: 'Get all channels',
},
+ {
+ name: 'Unarchive',
+ value: 'unarchive',
+ description: 'Unarchive a channel',
+ },
{
name: 'Update',
value: 'update',
@@ -64,7 +79,7 @@ export const channelFields = [
},
},
required: true,
- description: 'The id of the workspace.',
+ description: 'The ID of the workspace.',
},
{
displayName: 'Name',
@@ -156,28 +171,28 @@ export const channelFields = [
},
],
default: 0,
- description: 'The color of the channel',
+ description: 'The color of the channel.',
},
{
displayName: 'Description',
name: 'description',
type: 'string',
default: '',
- description: 'The description of the channel',
+ description: 'The description of the channel.',
},
{
displayName: 'Public',
name: 'public',
type: 'boolean',
default: false,
- description: 'If enabled, the channel will be marked as public',
+ description: 'If enabled, the channel will be marked as public.',
},
{
displayName: 'Temp ID',
name: 'temp_id',
type: 'number',
default: -1,
- description: 'The temporary id of the channel. It needs to be a negative number.',
+ description: 'The temporary ID of the channel. It needs to be a negative number.',
},
{
displayName: 'User IDs',
@@ -194,8 +209,9 @@ export const channelFields = [
},
],
},
+
/* -------------------------------------------------------------------------- */
- /* channel:get */
+ /* channel:get/archive/unarchive/delete */
/* -------------------------------------------------------------------------- */
{
displayName: 'Channel ID',
@@ -205,7 +221,10 @@ export const channelFields = [
displayOptions: {
show: {
operation: [
+ 'archive',
+ 'delete',
'get',
+ 'unarchive',
],
resource: [
'channel',
@@ -213,8 +232,9 @@ export const channelFields = [
},
},
required: true,
- description: 'The ID of the channel',
+ description: 'The ID of the channel.',
},
+
/* -------------------------------------------------------------------------- */
/* channel:getAll */
/* -------------------------------------------------------------------------- */
@@ -302,7 +322,7 @@ export const channelFields = [
name: 'archived',
type: 'boolean',
default: false,
- description: 'If enabled, only archived conversations are returned',
+ description: 'If enabled, only archived conversations are returned.',
},
],
},
@@ -400,28 +420,28 @@ export const channelFields = [
},
],
default: 0,
- description: 'The color of the channel',
+ description: 'The color of the channel.',
},
{
displayName: 'Description',
name: 'description',
type: 'string',
default: '',
- description: 'The description of the channel',
+ description: 'The description of the channel.',
},
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
- description: 'The name of the channel',
+ description: 'The name of the channel.',
},
{
displayName: 'Public',
name: 'public',
type: 'boolean',
default: false,
- description: 'If enabled, the channel will be marked as public',
+ description: 'If enabled, the channel will be marked as public.',
},
],
},
diff --git a/packages/nodes-base/nodes/Twist/CommentDescription.ts b/packages/nodes-base/nodes/Twist/CommentDescription.ts
new file mode 100644
index 0000000000..0aa0500d1a
--- /dev/null
+++ b/packages/nodes-base/nodes/Twist/CommentDescription.ts
@@ -0,0 +1,561 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+export const commentOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'comment',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ description: 'Create a new comment to a thread',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ description: 'Delete a comment',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Get information about a comment',
+ },
+ {
+ name: 'Get All',
+ value: 'getAll',
+ description: 'Get all comments',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ description: 'Update a comment',
+ },
+ ],
+ default: 'create',
+ description: 'The operation to perform.',
+ },
+] as INodeProperties[];
+
+export const commentFields = [
+ /*-------------------------------------------------------------------------- */
+ /* comment:create */
+ /* ------------------------------------------------------------------------- */
+ {
+ displayName: 'Thread ID',
+ name: 'threadId',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: [
+ 'create',
+ ],
+ resource: [
+ 'comment',
+ ],
+ },
+ },
+ required: true,
+ description: 'The ID of the thread.',
+ },
+ {
+ displayName: 'Content',
+ name: 'content',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: [
+ 'create',
+ ],
+ resource: [
+ 'comment',
+ ],
+ },
+ },
+ required: true,
+ description: 'The content of the comment.',
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'comment',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Actions',
+ name: 'actionsUi',
+ type: 'fixedCollection',
+ placeholder: 'Add Action',
+ typeOptions: {
+ multipleValues: true,
+ },
+ options: [
+ {
+ displayName: 'Action',
+ name: 'actionValues',
+ values: [
+ {
+ displayName: 'Action',
+ name: 'action',
+ type: 'options',
+ description: 'The action of the button.',
+ options: [
+ {
+ name: 'Open URL',
+ value: 'open_url',
+ },
+ {
+ name: 'Prefill Message',
+ value: 'prefill_message',
+ },
+ {
+ name: 'Send Reply',
+ value: 'send_reply',
+ },
+ ],
+ default: '',
+ },
+ {
+ displayName: 'Button Text',
+ name: 'button_text',
+ type: 'string',
+ description: 'The text for the action button.',
+ default: '',
+ },
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ displayOptions: {
+ show: {
+ action: [
+ 'send_reply',
+ 'prefill_message',
+ ],
+ },
+ },
+ description: 'The text for the action button.',
+ default: '',
+ },
+ {
+ displayName: 'Type',
+ name: 'type',
+ type: 'options',
+ description: 'The type of the button. (Currently only action
is available).',
+ options: [
+ {
+ name: 'Action',
+ value: 'action',
+ },
+ ],
+ default: '',
+ },
+ {
+ displayName: 'URL',
+ name: 'url',
+ type: 'string',
+ displayOptions: {
+ show: {
+ action: [
+ 'open_url',
+ ],
+ },
+ },
+ description: 'URL to redirect.',
+ default: '',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Attachments',
+ name: 'binaryProperties',
+ type: 'string',
+ default: 'data',
+ description: 'Name of the property that holds the binary data. Multiple can be defined separated by comma.',
+ },
+ {
+ displayName: 'Direct Mentions',
+ name: 'direct_mentions',
+ type: 'multiOptions',
+ typeOptions: {
+ loadOptionsMethod: 'getUsers',
+ loadOptionsDependsOn: [
+ 'workspaceId',
+ ],
+ },
+ default: [],
+ description: 'The users that are directly mentioned.',
+ },
+ {
+ displayName: 'Mark thread position',
+ name: 'mark_thread_position',
+ type: 'boolean',
+ default: true,
+ description: 'By default, the position of the thread is marked.',
+ },
+ {
+ displayName: 'Recipients',
+ name: 'recipients',
+ type: 'multiOptions',
+ typeOptions: {
+ loadOptionsMethod: 'getUsers',
+ loadOptionsDependsOn: [
+ 'workspaceId',
+ ],
+ },
+ default: [],
+ description: 'The users that will attached to the comment.',
+ },
+ {
+ displayName: 'Temporary ID',
+ name: 'temp_id',
+ type: 'number',
+ default: 0,
+ description: 'The temporary ID of the comment.',
+ },
+ {
+ displayName: 'Send as integration',
+ name: 'send_as_integration',
+ type: 'boolean',
+ default: false,
+ description: 'Displays the integration as the comment creator.',
+ },
+ ],
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* comment:get/delete */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Comment ID',
+ name: 'commentId',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: [
+ 'get',
+ 'delete',
+ ],
+ resource: [
+ 'comment',
+ ],
+ },
+ },
+ required: true,
+ description: 'The ID of the comment.',
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* comment:getAll */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Thread ID',
+ name: 'threadId',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'comment',
+ ],
+ },
+ },
+ required: true,
+ description: 'The ID of the channel.',
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ resource: [
+ 'comment',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ resource: [
+ 'comment',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 100,
+ },
+ default: 50,
+ description: 'How many results to return.',
+ },
+ {
+ displayName: 'Filters',
+ name: 'filters',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'comment',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'As IDs',
+ name: 'as_ids',
+ type: 'boolean',
+ default: false,
+ description: 'If enabled, only the ids of the comments are returned.',
+ },
+ {
+ displayName: 'Ending Object Index',
+ name: 'to_obj_index',
+ type: 'number',
+ default: 50,
+ description: 'Limit comments ending at the specified object index.',
+ },
+ {
+ displayName: 'Newer Than',
+ name: 'newer_than_ts',
+ type: 'dateTime',
+ default: '',
+ description: 'Limits comments to those newer when the specified Unix time.',
+ },
+ {
+ displayName: 'Older Than',
+ name: 'older_than_ts',
+ type: 'dateTime',
+ default: '',
+ description: 'Limits comments to those older than the specified Unix time.',
+ },
+ {
+ displayName: 'Order By',
+ name: 'order_by',
+ type: 'options',
+ options: [
+ {
+ name: 'ASC',
+ value: 'ASC',
+ },
+ {
+ name: 'DESC',
+ value: 'DESC',
+ },
+ ],
+ default: 'ASC',
+ description: 'The order of the comments returned - one of DESC or ASC.',
+ },
+ {
+ displayName: 'Starting Object Index',
+ name: 'from_obj_index',
+ type: 'number',
+ default: 0,
+ description: 'Limit comments starting at the specified object index.',
+ },
+ ],
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* comment:update */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Comment ID',
+ name: 'commentId',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: [
+ 'update',
+ ],
+ resource: [
+ 'comment',
+ ],
+ },
+ },
+ required: true,
+ description: 'The ID of the comment.',
+ },
+ {
+ displayName: 'Update Fields',
+ name: 'updateFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'comment',
+ ],
+ operation: [
+ 'update',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Actions',
+ name: 'actionsUi',
+ type: 'fixedCollection',
+ placeholder: 'Add Action',
+ typeOptions: {
+ multipleValues: true,
+ },
+ options: [
+ {
+ displayName: 'Action',
+ name: 'actionValues',
+ values: [
+ {
+ displayName: 'Action',
+ name: 'action',
+ type: 'options',
+ description: 'The action of the button.',
+ options: [
+ {
+ name: 'Open URL',
+ value: 'open_url',
+ },
+ {
+ name: 'Prefill Message',
+ value: 'prefill_message',
+ },
+ {
+ name: 'Send Reply',
+ value: 'send_reply',
+ },
+ ],
+ default: '',
+ },
+ {
+ displayName: 'Button Text',
+ name: 'button_text',
+ type: 'string',
+ description: 'The text for the action button.',
+ default: '',
+ },
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ displayOptions: {
+ show: {
+ action: [
+ 'send_reply',
+ 'prefill_message',
+ ],
+ },
+ },
+ description: 'The text for the action button.',
+ default: '',
+ },
+ {
+ displayName: 'Type',
+ name: 'type',
+ type: 'options',
+ description: 'The type of the button. (Currently only action
is available).',
+ options: [
+ {
+ name: 'Action',
+ value: 'action',
+ },
+ ],
+ default: '',
+ },
+ {
+ displayName: 'URL',
+ name: 'url',
+ type: 'string',
+ displayOptions: {
+ show: {
+ action: [
+ 'open_url',
+ ],
+ },
+ },
+ description: 'URL to redirect.',
+ default: '',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Attachments',
+ name: 'binaryProperties',
+ type: 'string',
+ default: 'data',
+ description: 'Name of the property that holds the binary data. Multiple can be defined separated by comma.',
+ },
+ {
+ displayName: 'Content',
+ name: 'content',
+ type: 'string',
+ default: '',
+ description: 'The content of the comment.',
+ },
+ {
+ displayName: 'Direct Mentions',
+ name: 'direct_mentions',
+ type: 'multiOptions',
+ typeOptions: {
+ loadOptionsMethod: 'getUsers',
+ loadOptionsDependsOn: [
+ 'workspaceId',
+ ],
+ },
+ default: [],
+ description: 'The users that are directly mentioned.',
+ },
+ ],
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/Twist/MessageConversationDescription.ts b/packages/nodes-base/nodes/Twist/MessageConversationDescription.ts
index d81f1254dc..c08201339e 100644
--- a/packages/nodes-base/nodes/Twist/MessageConversationDescription.ts
+++ b/packages/nodes-base/nodes/Twist/MessageConversationDescription.ts
@@ -20,6 +20,26 @@ export const messageConversationOperations = [
value: 'create',
description: 'Create a message in a conversation',
},
+ {
+ name: 'Delete',
+ value: 'delete',
+ description: 'Delete a message in a conversation',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Get a message in a conversation',
+ },
+ {
+ name: 'Get All',
+ value: 'getAll',
+ description: 'Get all messages in a conversation',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ description: 'Update a message in a conversation',
+ },
],
default: 'create',
description: 'The operation to perform.',
@@ -91,7 +111,7 @@ export const messageConversationFields = [
],
},
},
- description: `The content of the new message. Mentions can be used as [Name](twist-mention://user_id) for users or [Group name](twist-group-mention://group_id) for groups.`,
+ description: 'The content of the new message. Mentions can be used as [Name](twist-mention://user_id)
for users or [Group name](twist-group-mention://group_id)
for groups.',
},
{
displayName: 'Additional Fields',
@@ -108,7 +128,7 @@ export const messageConversationFields = [
},
},
default: {},
- description: 'Other options to set',
+ description: 'Other options to set.',
placeholder: 'Add options',
options: [
{
@@ -128,7 +148,7 @@ export const messageConversationFields = [
displayName: 'Action',
name: 'action',
type: 'options',
- description: 'The action of the button',
+ description: 'The action of the button.',
options: [
{
name: 'Open URL',
@@ -171,7 +191,7 @@ export const messageConversationFields = [
displayName: 'Type',
name: 'type',
type: 'options',
- description: 'The type of the button, for now just action is available.',
+ description: 'The type of the button. (Currently only action
is available).',
options: [
{
name: 'Action',
@@ -191,7 +211,7 @@ export const messageConversationFields = [
],
},
},
- description: 'URL to redirect',
+ description: 'URL to redirect.',
default: '',
},
],
@@ -213,7 +233,7 @@ export const messageConversationFields = [
loadOptionsMethod: 'getUsers',
},
default: [],
- description: `The users that are directly mentioned`,
+ description: 'The users that are directly mentioned.',
},
// {
// displayName: 'Direct Group Mentions ',
@@ -223,8 +243,289 @@ export const messageConversationFields = [
// loadOptionsMethod: 'getGroups',
// },
// default: [],
- // description: `The groups that are directly mentioned`,
+ // description: 'The groups that are directly mentioned.',
// },
],
},
+
+ /* -------------------------------------------------------------------------- */
+ /* messageConversation:getAll */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Workspace ID',
+ name: 'workspaceId',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getWorkspaces',
+ },
+ default: '',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'messageConversation',
+ ],
+ },
+ },
+ required: true,
+ description: 'The ID of the workspace.',
+ },
+ {
+ displayName: 'Conversation ID',
+ name: 'conversationId',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getConversations',
+ loadOptionsDependsOn: [
+ 'workspaceId',
+ ],
+ },
+ default: '',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'messageConversation',
+ ],
+ },
+ },
+ required: true,
+ description: 'The ID of the conversation.',
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'messageConversation',
+ ],
+ },
+ },
+ default: {},
+ description: 'Other options to set.',
+ options: [
+ {
+ displayName: 'Ending Object Index',
+ name: 'to_obj_index',
+ type: 'number',
+ default: 50,
+ description: 'Limit messages ending at the specified object index.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 50,
+ description: 'Limits the number of messages returned.',
+ },
+ {
+ displayName: 'Order By',
+ name: 'order_by',
+ type: 'options',
+ default: 'ASC',
+ description: 'The order of the conversations returned - one of DESC or ASC.',
+ options: [
+ {
+ name: 'ASC',
+ value: 'ASC',
+ },
+ {
+ name: 'DESC',
+ value: 'DESC',
+ },
+ ],
+ },
+ {
+ displayName: 'Starting Object Index',
+ name: 'from_obj_index',
+ type: 'number',
+ default: 0,
+ description: 'Limit messages starting at the specified object index.',
+ },
+ ],
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* messageConversation:get/delete/update */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Message ID',
+ name: 'id',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: [
+ 'delete',
+ 'get',
+ ],
+ resource: [
+ 'messageConversation',
+ ],
+ },
+ },
+ required: true,
+ description: 'The ID of the conversation message.',
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* messageConversation:update */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Conversation Message ID',
+ name: 'id',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: [
+ 'update',
+ ],
+ resource: [
+ 'messageConversation',
+ ],
+ },
+ },
+ required: true,
+ description: 'The ID of the conversation message.',
+ },
+ {
+ displayName: 'Update Fields',
+ name: 'updateFields',
+ type: 'collection',
+ displayOptions: {
+ show: {
+ operation: [
+ 'update',
+ ],
+ resource: [
+ 'messageConversation',
+ ],
+ },
+ },
+ default: {},
+ description: 'Other options to set.',
+ options: [
+ {
+ displayName: 'Actions',
+ name: 'actionsUi',
+ type: 'fixedCollection',
+ placeholder: 'Add Action',
+ typeOptions: {
+ multipleValues: true,
+ },
+ options: [
+ {
+ displayName: 'Action',
+ name: 'actionValues',
+ values: [
+ {
+ displayName: 'Action',
+ name: 'action',
+ type: 'options',
+ description: 'The action of the button.',
+ options: [
+ {
+ name: 'Open URL',
+ value: 'open_url',
+ },
+ {
+ name: 'Prefill Message',
+ value: 'prefill_message',
+ },
+ {
+ name: 'Send Reply',
+ value: 'send_reply',
+ },
+ ],
+ default: '',
+ },
+ {
+ displayName: 'Button Text',
+ name: 'button_text',
+ type: 'string',
+ description: 'The text for the action button.',
+ default: '',
+ },
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ displayOptions: {
+ show: {
+ action: [
+ 'send_reply',
+ 'prefill_message',
+ ],
+ },
+ },
+ description: 'The text for the action button.',
+ default: '',
+ },
+ {
+ displayName: 'Type',
+ name: 'type',
+ type: 'options',
+ description: 'The type of the button. (Currently only action
is available).',
+ options: [
+ {
+ name: 'Action',
+ value: 'action',
+ },
+ ],
+ default: '',
+ },
+ {
+ displayName: 'URL',
+ name: 'url',
+ type: 'string',
+ displayOptions: {
+ show: {
+ action: [
+ 'open_url',
+ ],
+ },
+ },
+ description: 'URL to redirect.',
+ default: '',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Attachments',
+ name: 'binaryProperties',
+ type: 'string',
+ default: 'data',
+ description: 'Name of the property that holds the binary data. Multiple can be defined separated by comma.',
+ },
+ {
+ displayName: 'Content',
+ name: 'content',
+ type: 'string',
+ default: '',
+ description: 'The content of the new message. Mentions can be used as [Name](twist-mention://user_id)
for users or [Group name](twist-group-mention://group_id)
for groups.',
+ },
+ {
+ displayName: 'Direct Mentions',
+ name: 'direct_mentions',
+ type: 'multiOptions',
+ typeOptions: {
+ loadOptionsMethod: 'getUsers',
+ },
+ default: [],
+ description: 'The users that are directly mentioned.',
+ },
+ ],
+ },
] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/Twist/ThreadDescription.ts b/packages/nodes-base/nodes/Twist/ThreadDescription.ts
new file mode 100644
index 0000000000..4ce8f48a47
--- /dev/null
+++ b/packages/nodes-base/nodes/Twist/ThreadDescription.ts
@@ -0,0 +1,567 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+export const threadOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'thread',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ description: 'Create a new thread in a channel',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ description: 'Delete a thread',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Get information about a thread',
+ },
+ {
+ name: 'Get All',
+ value: 'getAll',
+ description: 'Get all threads',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ description: 'Update a thread',
+ },
+ ],
+ default: 'create',
+ description: 'The operation to perform.',
+ },
+] as INodeProperties[];
+
+export const threadFields = [
+ /*-------------------------------------------------------------------------- */
+ /* thread:create */
+ /* ------------------------------------------------------------------------- */
+ {
+ displayName: 'Channel ID',
+ name: 'channelId',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: [
+ 'create',
+ ],
+ resource: [
+ 'thread',
+ ],
+ },
+ },
+ required: true,
+ description: 'The ID of the channel.',
+ },
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: [
+ 'create',
+ ],
+ resource: [
+ 'thread',
+ ],
+ },
+ },
+ required: true,
+ description: 'The title of the new thread (1 < length < 300).',
+ },
+ {
+ displayName: 'Content',
+ name: 'content',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: [
+ 'create',
+ ],
+ resource: [
+ 'thread',
+ ],
+ },
+ },
+ required: true,
+ description: 'The content of the thread.',
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'thread',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Actions',
+ name: 'actionsUi',
+ type: 'fixedCollection',
+ placeholder: 'Add Action',
+ typeOptions: {
+ multipleValues: true,
+ },
+ options: [
+ {
+ displayName: 'Action',
+ name: 'actionValues',
+ values: [
+ {
+ displayName: 'Action',
+ name: 'action',
+ type: 'options',
+ description: 'The action of the button.',
+ options: [
+ {
+ name: 'Open URL',
+ value: 'open_url',
+ },
+ {
+ name: 'Prefill Message',
+ value: 'prefill_message',
+ },
+ {
+ name: 'Send Reply',
+ value: 'send_reply',
+ },
+ ],
+ default: '',
+ },
+ {
+ displayName: 'Button Text',
+ name: 'button_text',
+ type: 'string',
+ description: 'The text for the action button.',
+ default: '',
+ },
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ displayOptions: {
+ show: {
+ action: [
+ 'send_reply',
+ 'prefill_message',
+ ],
+ },
+ },
+ description: 'The text for the action button.',
+ default: '',
+ },
+ {
+ displayName: 'Type',
+ name: 'type',
+ type: 'options',
+ description: 'The type of the button. (Currently only action
is available).',
+ options: [
+ {
+ name: 'Action',
+ value: 'action',
+ },
+ ],
+ default: '',
+ },
+ {
+ displayName: 'URL',
+ name: 'url',
+ type: 'string',
+ displayOptions: {
+ show: {
+ action: [
+ 'open_url',
+ ],
+ },
+ },
+ description: 'URL to redirect.',
+ default: '',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Attachments',
+ name: 'binaryProperties',
+ type: 'string',
+ default: 'data',
+ description: 'Name of the property that holds the binary data. Multiple can be defined separated by comma.',
+ },
+ {
+ displayName: 'Direct Mentions',
+ name: 'direct_mentions',
+ type: 'multiOptions',
+ typeOptions: {
+ loadOptionsMethod: 'getUsers',
+ loadOptionsDependsOn: [
+ 'workspaceId',
+ ],
+ },
+ default: [],
+ description: 'The users that are directly mentioned.',
+ },
+ {
+ displayName: 'Recipients',
+ name: 'recipients',
+ type: 'multiOptions',
+ typeOptions: {
+ loadOptionsMethod: 'getUsers',
+ loadOptionsDependsOn: [
+ 'workspaceId',
+ ],
+ },
+ default: [],
+ description: 'The users that will attached to the thread.',
+ },
+ {
+ displayName: 'Send as integration',
+ name: 'send_as_integration',
+ type: 'boolean',
+ default: false,
+ description: 'Displays the integration as the thread creator.',
+ },
+ {
+ displayName: 'Temporary ID',
+ name: 'temp_id',
+ type: 'number',
+ default: 0,
+ description: 'The temporary ID of the thread.',
+ },
+ ],
+ },
+ /* -------------------------------------------------------------------------- */
+ /* thread:get/delete */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Thread ID',
+ name: 'threadId',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: [
+ 'get',
+ 'delete',
+ ],
+ resource: [
+ 'thread',
+ ],
+ },
+ },
+ required: true,
+ description: 'The ID of the thread.',
+ },
+ /* -------------------------------------------------------------------------- */
+ /* thread:getAll */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Channel ID',
+ name: 'channelId',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'thread',
+ ],
+ },
+ },
+ required: true,
+ description: 'The ID of the channel.',
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ resource: [
+ 'thread',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ resource: [
+ 'thread',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 100,
+ },
+ default: 50,
+ description: 'How many results to return.',
+ },
+ {
+ displayName: 'Filters',
+ name: 'filters',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'thread',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'As IDs',
+ name: 'as_ids',
+ type: 'boolean',
+ default: false,
+ description: 'If enabled, only the IDs of the threads are returned.',
+ },
+ {
+ displayName: 'Filter By',
+ name: 'filter_by',
+ type: 'options',
+ options: [
+ {
+ name: 'Attached to me',
+ value: 'attached_to_me',
+ },
+ {
+ name: 'Everyone',
+ value: 'everyone',
+ },
+ {
+ name: 'Starred',
+ value: 'is_starred',
+ },
+ ],
+ default: '',
+ description: 'A filter can be one of attached_to_me
, everyone
and is_starred
.',
+ },
+ {
+ displayName: 'Newer Than',
+ name: 'newer_than_ts',
+ type: 'dateTime',
+ default: '',
+ description: 'Limits threads to those newer when the specified Unix time.',
+ },
+ {
+ displayName: 'Older Than',
+ name: 'older_than_ts',
+ type: 'dateTime',
+ default: '',
+ description: 'Limits threads to those older than the specified Unix time.',
+ },
+ ],
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* thread:update */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Thread ID',
+ name: 'threadId',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ operation: [
+ 'update',
+ ],
+ resource: [
+ 'thread',
+ ],
+ },
+ },
+ required: true,
+ description: 'The ID of the thread.',
+ },
+ {
+ displayName: 'Update Fields',
+ name: 'updateFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'thread',
+ ],
+ operation: [
+ 'update',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Actions',
+ name: 'actionsUi',
+ type: 'fixedCollection',
+ placeholder: 'Add Action',
+ typeOptions: {
+ multipleValues: true,
+ },
+ options: [
+ {
+ displayName: 'Action',
+ name: 'actionValues',
+ values: [
+ {
+ displayName: 'Action',
+ name: 'action',
+ type: 'options',
+ description: 'The action of the button.',
+ options: [
+ {
+ name: 'Open URL',
+ value: 'open_url',
+ },
+ {
+ name: 'Prefill Message',
+ value: 'prefill_message',
+ },
+ {
+ name: 'Send Reply',
+ value: 'send_reply',
+ },
+ ],
+ default: '',
+ },
+ {
+ displayName: 'Button Text',
+ name: 'button_text',
+ type: 'string',
+ description: 'The text for the action button.',
+ default: '',
+ },
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ displayOptions: {
+ show: {
+ action: [
+ 'send_reply',
+ 'prefill_message',
+ ],
+ },
+ },
+ description: 'The text for the action button.',
+ default: '',
+ },
+ {
+ displayName: 'Type',
+ name: 'type',
+ type: 'options',
+ description: 'The type of the button. (Currently only action
is available).',
+ options: [
+ {
+ name: 'Action',
+ value: 'action',
+ },
+ ],
+ default: '',
+ },
+ {
+ displayName: 'URL',
+ name: 'url',
+ type: 'string',
+ displayOptions: {
+ show: {
+ action: [
+ 'open_url',
+ ],
+ },
+ },
+ description: 'URL to redirect.',
+ default: '',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Attachments',
+ name: 'binaryProperties',
+ type: 'string',
+ default: 'data',
+ description: 'Name of the property that holds the binary data. Multiple can be defined separated by comma.',
+ },
+ {
+ displayName: 'Content',
+ name: 'content',
+ type: 'string',
+ default: '',
+ description: 'The content of the thread.',
+ },
+ {
+ displayName: 'Direct Mentions',
+ name: 'direct_mentions',
+ type: 'multiOptions',
+ typeOptions: {
+ loadOptionsMethod: 'getUsers',
+ loadOptionsDependsOn: [
+ 'workspaceId',
+ ],
+ },
+ default: [],
+ description: 'The users that are directly mentioned.',
+ },
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ default: '',
+ description: 'The title of the thread (1 < length < 300).',
+ },
+ ],
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/Twist/Twist.node.ts b/packages/nodes-base/nodes/Twist/Twist.node.ts
index d2fe9f18dd..3d9c70092c 100644
--- a/packages/nodes-base/nodes/Twist/Twist.node.ts
+++ b/packages/nodes-base/nodes/Twist/Twist.node.ts
@@ -29,7 +29,16 @@ import {
messageConversationOperations,
} from './MessageConversationDescription';
+import {
+ threadFields,
+ threadOperations
+} from './ThreadDescription';
+import {
+ commentFields,
+ commentOperations
+} from './CommentDescription';
import uuid = require('uuid');
+import * as moment from 'moment';
export class Twist implements INodeType {
description: INodeTypeDescription = {
@@ -62,18 +71,30 @@ export class Twist implements INodeType {
name: 'Channel',
value: 'channel',
},
+ {
+ name: 'Comment',
+ value: 'comment',
+ },
{
name: 'Message Conversation',
value: 'messageConversation',
},
+ {
+ name: 'Thread',
+ value: 'thread',
+ },
],
default: 'messageConversation',
description: 'The resource to operate on.',
},
...channelOperations,
...channelFields,
+ ...commentOperations,
+ ...commentFields,
...messageConversationOperations,
...messageConversationFields,
+ ...threadOperations,
+ ...threadFields,
],
};
@@ -169,10 +190,15 @@ export class Twist implements INodeType {
responseData = await twistApiRequest.call(this, 'POST', '/channels/add', body);
}
+ //https://developer.twist.com/v3/#remove-channel
+ if (operation === 'delete') {
+ qs.id = this.getNodeParameter('channelId', i) as string;
+
+ responseData = await twistApiRequest.call(this, 'POST', '/channels/remove', {}, qs);
+ }
//https://developer.twist.com/v3/#get-channel
if (operation === 'get') {
- const channelId = this.getNodeParameter('channelId', i) as string;
- qs.id = channelId;
+ qs.id = this.getNodeParameter('channelId', i) as string;
responseData = await twistApiRequest.call(this, 'GET', '/channels/getone', {}, qs);
}
@@ -202,6 +228,190 @@ export class Twist implements INodeType {
responseData = await twistApiRequest.call(this, 'POST', '/channels/update', body);
}
+ //https://developer.twist.com/v3/#archive-channel
+ if (operation === 'archive') {
+ qs.id = this.getNodeParameter('channelId', i) as string;
+
+ responseData = await twistApiRequest.call(this, 'POST', '/channels/archive', {}, qs);
+ }
+ //https://developer.twist.com/v3/#unarchive-channel
+ if (operation === 'unarchive') {
+ qs.id = this.getNodeParameter('channelId', i) as string;
+
+ responseData = await twistApiRequest.call(this, 'POST', '/channels/unarchive', {}, qs);
+ }
+ }
+ if (resource === 'comment') {
+ //https://developer.twist.com/v3/#add-comment
+ if (operation === 'create') {
+ const threadId = this.getNodeParameter('threadId', i) as string;
+ const content = this.getNodeParameter('content', i) as string;
+ const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
+ const body: IDataObject = {
+ thread_id: threadId,
+ content,
+ };
+ Object.assign(body, additionalFields);
+
+ if (body.actionsUi) {
+ const actions = (body.actionsUi as IDataObject).actionValues as IDataObject[];
+
+ if (actions) {
+ body.actions = actions;
+ delete body.actionsUi;
+ }
+ }
+
+ if (body.binaryProperties) {
+ const binaryProperties = (body.binaryProperties as string).split(',') as string[];
+
+ const attachments: IDataObject[] = [];
+
+ for (const binaryProperty of binaryProperties) {
+
+ const item = items[i].binary as IBinaryKeyData;
+
+ const binaryData = item[binaryProperty] as IBinaryData;
+
+ if (binaryData === undefined) {
+ throw new Error(`No binary data property "${binaryProperty}" does not exists on item!`);
+ }
+
+ attachments.push(await twistApiRequest.call(
+ this,
+ 'POST',
+ '/attachments/upload',
+ {},
+ {},
+ {
+ formData: {
+ file_name: {
+ value: Buffer.from(binaryData.data, BINARY_ENCODING),
+ options: {
+ filename: binaryData.fileName,
+ },
+ },
+ attachment_id: uuid(),
+ },
+ },
+ ));
+ }
+
+ body.attachments = attachments;
+ }
+
+ if (body.direct_mentions) {
+ const directMentions: string[] = [];
+ for (const directMention of body.direct_mentions as number[]) {
+ directMentions.push(`[name](twist-mention://${directMention})`);
+ }
+ body.content = `${directMentions.join(' ')} ${body.content}`;
+ }
+
+ responseData = await twistApiRequest.call(this, 'POST', '/comments/add', body);
+ }
+ //https://developer.twist.com/v3/#remove-comment
+ if (operation === 'delete') {
+ qs.id = this.getNodeParameter('commentId', i) as string;
+
+ responseData = await twistApiRequest.call(this, 'POST', '/comments/remove', {}, qs);
+ }
+ //https://developer.twist.com/v3/#get-comment
+ if (operation === 'get') {
+ qs.id = this.getNodeParameter('commentId', i) as string;
+
+ responseData = await twistApiRequest.call(this, 'GET', '/comments/getone', {}, qs);
+ responseData = responseData?.comment;
+ }
+ //https://developer.twist.com/v3/#get-all-comments
+ if (operation === 'getAll') {
+ const threadId = this.getNodeParameter('threadId', i) as string;
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+ const filters = this.getNodeParameter('filters', i) as IDataObject;
+ qs.thread_id = threadId;
+
+ Object.assign(qs, filters);
+ if (!returnAll) {
+ qs.limit = this.getNodeParameter('limit', i) as number;
+ }
+ if (qs.older_than_ts) {
+ qs.older_than_ts = moment(qs.older_than_ts as string).unix();
+ }
+ if (qs.newer_than_ts) {
+ qs.newer_than_ts = moment(qs.newer_than_ts as string).unix();
+ }
+
+ responseData = await twistApiRequest.call(this, 'GET', '/comments/get', {}, qs);
+ if (qs.as_ids) {
+ responseData = (responseData as Array).map(id => ({ ID: id }));
+ }
+ }
+ //https://developer.twist.com/v3/#update-comment
+ if (operation === 'update') {
+ const commentId = this.getNodeParameter('commentId', i) as string;
+ const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
+ const body: IDataObject = {
+ id: commentId,
+ };
+ Object.assign(body, updateFields);
+
+ if (body.actionsUi) {
+ const actions = (body.actionsUi as IDataObject).actionValues as IDataObject[];
+
+ if (actions) {
+ body.actions = actions;
+ delete body.actionsUi;
+ }
+ }
+
+ if (body.binaryProperties) {
+ const binaryProperties = (body.binaryProperties as string).split(',') as string[];
+
+ const attachments: IDataObject[] = [];
+
+ for (const binaryProperty of binaryProperties) {
+
+ const item = items[i].binary as IBinaryKeyData;
+
+ const binaryData = item[binaryProperty] as IBinaryData;
+
+ if (binaryData === undefined) {
+ throw new Error(`No binary data property "${binaryProperty}" does not exists on item!`);
+ }
+
+ attachments.push(await twistApiRequest.call(
+ this,
+ 'POST',
+ '/attachments/upload',
+ {},
+ {},
+ {
+ formData: {
+ file_name: {
+ value: Buffer.from(binaryData.data, BINARY_ENCODING),
+ options: {
+ filename: binaryData.fileName,
+ },
+ },
+ attachment_id: uuid(),
+ },
+ },
+ ));
+ }
+
+ body.attachments = attachments;
+ }
+
+ if (body.direct_mentions) {
+ const directMentions: string[] = [];
+ for (const directMention of body.direct_mentions as number[]) {
+ directMentions.push(`[name](twist-mention://${directMention})`);
+ }
+ body.content = `${directMentions.join(' ')} ${body.content}`;
+ }
+
+ responseData = await twistApiRequest.call(this, 'POST', '/comments/update', body);
+ }
}
if (resource === 'messageConversation') {
//https://developer.twist.com/v3/#add-message-to-conversation
@@ -244,7 +454,7 @@ export class Twist implements INodeType {
attachments.push(await twistApiRequest.call(
this,
'POST',
- `/attachments/upload`,
+ '/attachments/upload',
{},
{},
{
@@ -265,11 +475,11 @@ export class Twist implements INodeType {
}
if (body.direct_mentions) {
- const direcMentions: string[] = [];
+ const directMentions: string[] = [];
for (const directMention of body.direct_mentions as number[]) {
- direcMentions.push(`[name](twist-mention://${directMention})`);
+ directMentions.push(`[name](twist-mention://${directMention})`);
}
- body.content = `${direcMentions.join(' ')} ${body.content}`;
+ body.content = `${directMentions.join(' ')} ${body.content}`;
}
// if (body.direct_group_mentions) {
@@ -282,6 +492,266 @@ export class Twist implements INodeType {
responseData = await twistApiRequest.call(this, 'POST', '/conversation_messages/add', body);
}
+ //https://developer.twist.com/v3/#get-message
+ if (operation === 'get') {
+ qs.id = this.getNodeParameter('id', i) as string;
+
+ responseData = await twistApiRequest.call(this, 'GET', '/conversation_messages/getone', {}, qs);
+ }
+ //https://developer.twist.com/v3/#get-all-messages
+ if (operation === 'getAll') {
+ const conversationId = this.getNodeParameter('conversationId', i) as string;
+ const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
+ qs.conversation_id = conversationId;
+ Object.assign(qs, additionalFields);
+
+ responseData = await twistApiRequest.call(this, 'GET', '/conversation_messages/get', {}, qs);
+ }
+ //https://developer.twist.com/v3/#remove-message-from-conversation
+ if (operation === 'delete') {
+ qs.id = this.getNodeParameter('id', i) as string;
+
+ responseData = await twistApiRequest.call(this, 'POST', '/conversation_messages/remove', {}, qs);
+ }
+ //https://developer.twist.com/v3/#update-message-in-conversation
+ if (operation === 'update') {
+ const id = this.getNodeParameter('id', i) as string;
+ const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
+ const body: IDataObject = {
+ id,
+ };
+ Object.assign(body, updateFields);
+
+ if (body.actionsUi) {
+ const actions = (body.actionsUi as IDataObject).actionValues as IDataObject[];
+
+ if (actions) {
+ body.actions = actions;
+ delete body.actionsUi;
+ }
+ }
+
+ if (body.binaryProperties) {
+ const binaryProperties = (body.binaryProperties as string).split(',') as string[];
+
+ const attachments: IDataObject[] = [];
+
+ for (const binaryProperty of binaryProperties) {
+
+ const item = items[i].binary as IBinaryKeyData;
+
+ const binaryData = item[binaryProperty] as IBinaryData;
+
+ if (binaryData === undefined) {
+ throw new Error(`No binary data property "${binaryProperty}" does not exists on item!`);
+ }
+
+ attachments.push(await twistApiRequest.call(
+ this,
+ 'POST',
+ '/attachments/upload',
+ {},
+ {},
+ {
+ formData: {
+ file_name: {
+ value: Buffer.from(binaryData.data, BINARY_ENCODING),
+ options: {
+ filename: binaryData.fileName,
+ },
+ },
+ attachment_id: uuid(),
+ },
+ },
+ ));
+ }
+
+ body.attachments = attachments;
+ }
+
+ if (body.direct_mentions) {
+ const directMentions: string[] = [];
+ for (const directMention of body.direct_mentions as number[]) {
+ directMentions.push(`[name](twist-mention://${directMention})`);
+ }
+ body.content = `${directMentions.join(' ')} ${body.content}`;
+ }
+
+ responseData = await twistApiRequest.call(this, 'POST', '/conversation_messages/update', body);
+ }
+ }
+ if (resource === 'thread') {
+ //https://developer.twist.com/v3/#add-thread
+ if (operation === 'create') {
+ const channelId = this.getNodeParameter('channelId', i) as string;
+ const title = this.getNodeParameter('title', i) as string;
+ const content = this.getNodeParameter('content', i) as string;
+ const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
+ const body: IDataObject = {
+ channel_id: channelId,
+ content,
+ title,
+ };
+ Object.assign(body, additionalFields);
+
+ if (body.actionsUi) {
+ const actions = (body.actionsUi as IDataObject).actionValues as IDataObject[];
+
+ if (actions) {
+ body.actions = actions;
+ delete body.actionsUi;
+ }
+ }
+
+ if (body.binaryProperties) {
+ const binaryProperties = (body.binaryProperties as string).split(',') as string[];
+
+ const attachments: IDataObject[] = [];
+
+ for (const binaryProperty of binaryProperties) {
+
+ const item = items[i].binary as IBinaryKeyData;
+
+ const binaryData = item[binaryProperty] as IBinaryData;
+
+ if (binaryData === undefined) {
+ throw new Error(`No binary data property "${binaryProperty}" does not exists on item!`);
+ }
+
+ attachments.push(await twistApiRequest.call(
+ this,
+ 'POST',
+ '/attachments/upload',
+ {},
+ {},
+ {
+ formData: {
+ file_name: {
+ value: Buffer.from(binaryData.data, BINARY_ENCODING),
+ options: {
+ filename: binaryData.fileName,
+ },
+ },
+ attachment_id: uuid(),
+ },
+ },
+ ));
+ }
+
+ body.attachments = attachments;
+ }
+
+ if (body.direct_mentions) {
+ const directMentions: string[] = [];
+ for (const directMention of body.direct_mentions as number[]) {
+ directMentions.push(`[name](twist-mention://${directMention})`);
+ }
+ body.content = `${directMentions.join(' ')} ${body.content}`;
+ }
+
+ responseData = await twistApiRequest.call(this, 'POST', '/threads/add', body);
+ }
+ //https://developer.twist.com/v3/#remove-thread
+ if (operation === 'delete') {
+ qs.id = this.getNodeParameter('threadId', i) as string;
+
+ responseData = await twistApiRequest.call(this, 'POST', '/threads/remove', {}, qs);
+ }
+ //https://developer.twist.com/v3/#get-thread
+ if (operation === 'get') {
+ qs.id = this.getNodeParameter('threadId', i) as string;
+
+ responseData = await twistApiRequest.call(this, 'GET', '/threads/getone', {}, qs);
+ }
+ //https://developer.twist.com/v3/#get-all-threads
+ if (operation === 'getAll') {
+ const channelId = this.getNodeParameter('channelId', i) as string;
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+ const filters = this.getNodeParameter('filters', i) as IDataObject;
+ qs.channel_id = channelId;
+
+ Object.assign(qs, filters);
+ if (!returnAll) {
+ qs.limit = this.getNodeParameter('limit', i) as number;
+ }
+ if (qs.older_than_ts) {
+ qs.older_than_ts = moment(qs.older_than_ts as string).unix();
+ }
+ if (qs.newer_than_ts) {
+ qs.newer_than_ts = moment(qs.newer_than_ts as string).unix();
+ }
+
+ responseData = await twistApiRequest.call(this, 'GET', '/threads/get', {}, qs);
+ if (qs.as_ids) {
+ responseData = (responseData as Array).map(id => ({ ID: id }));
+ }
+ }
+ //https://developer.twist.com/v3/#update-thread
+ if (operation === 'update') {
+ const threadId = this.getNodeParameter('threadId', i) as string;
+ const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
+ const body: IDataObject = {
+ id: threadId,
+ };
+ Object.assign(body, updateFields);
+
+ if (body.actionsUi) {
+ const actions = (body.actionsUi as IDataObject).actionValues as IDataObject[];
+
+ if (actions) {
+ body.actions = actions;
+ delete body.actionsUi;
+ }
+ }
+
+ if (body.binaryProperties) {
+ const binaryProperties = (body.binaryProperties as string).split(',') as string[];
+
+ const attachments: IDataObject[] = [];
+
+ for (const binaryProperty of binaryProperties) {
+
+ const item = items[i].binary as IBinaryKeyData;
+
+ const binaryData = item[binaryProperty] as IBinaryData;
+
+ if (binaryData === undefined) {
+ throw new Error(`No binary data property "${binaryProperty}" does not exists on item!`);
+ }
+
+ attachments.push(await twistApiRequest.call(
+ this,
+ 'POST',
+ '/attachments/upload',
+ {},
+ {},
+ {
+ formData: {
+ file_name: {
+ value: Buffer.from(binaryData.data, BINARY_ENCODING),
+ options: {
+ filename: binaryData.fileName,
+ },
+ },
+ attachment_id: uuid(),
+ },
+ },
+ ));
+ }
+
+ body.attachments = attachments;
+ }
+
+ if (body.direct_mentions) {
+ const directMentions: string[] = [];
+ for (const directMention of body.direct_mentions as number[]) {
+ directMentions.push(`[name](twist-mention://${directMention})`);
+ }
+ body.content = `${directMentions.join(' ')} ${body.content}`;
+ }
+
+ responseData = await twistApiRequest.call(this, 'POST', '/threads/update', body);
+ }
}
if (Array.isArray(responseData)) {
returnData.push.apply(returnData, responseData as IDataObject[]);