Extend Twist Node (#1721)

* Add get/getAll:messageConversation to Twist node

* Add delete:messageConversation to Twist node

* Add update:messageConversation to Twist node

* Add archive/unarchive/delete:channel to Twist node

* Add add/update/get/getAll/remove:Thread to Twist node

* Add add/update/get/getAll/remove:Comment to Twist node

* Lint fixes

* Fix operations's descriptions

* Enhance Twist node code

* Reorder attributes alphabetically

* Fix typos

* Fix the ouput of get:Comment operation

* Fix getAll:Comment & getAll:Thread operations outputs

* 🐛 Add missing scopes and remove not needed parameters

Co-authored-by: dali <servfrdali@yahoo.fr>
This commit is contained in:
Jan 2021-04-30 19:44:12 -05:00 committed by GitHub
parent fc54f7c82b
commit f79bc633c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 1946 additions and 25 deletions

View file

@ -6,7 +6,9 @@ import {
const scopes = [
'attachments:write',
'channels:remove',
'comments:remove',
'messages:remove',
'threads:remove',
'workspaces:read',
];

View file

@ -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.',
},
],
},

View file

@ -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 <code>action</code> 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 <code>action</code> 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[];

View file

@ -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 <code>[Name](twist-mention://user_id)</code> for users or <code>[Group name](twist-group-mention://group_id)</code> 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 <code>action</code> 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 <code>action</code> 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 <code>[Name](twist-mention://user_id)</code> for users or <code>[Group name](twist-group-mention://group_id)</code> for groups.',
},
{
displayName: 'Direct Mentions',
name: 'direct_mentions',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getUsers',
},
default: [],
description: 'The users that are directly mentioned.',
},
],
},
] as INodeProperties[];

View file

@ -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 <code>action</code> 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 <code>attached_to_me</code>, <code>everyone</code> and <code>is_starred</code>.',
},
{
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 <code>action</code> 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[];

View file

@ -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<number>).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<number>).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[]);