From 38ddcbe703bfa0c42b3a1451d75f8fb0b7bfb2c3 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Sun, 13 Sep 2020 05:08:43 -0400 Subject: [PATCH] :sparkles: Add Microsoft Teams Node (#931) * :sparkles: Microsoft Team node * :zap: Small fix * :zap: Small improvements * :zap: Small improvements * :zap: Small fix * :zap: Small improvements --- .../MicrosoftOAuth2Api.credentials.ts | 6 +- .../MicrosoftTeamsOAuth2Api.credentials.ts | 21 + .../Microsoft/Teams/ChannelDescription.ts | 381 ++++++++++++++++++ .../Teams/ChannelMessageDescription.ts | 220 ++++++++++ .../nodes/Microsoft/Teams/GenericFunctions.ts | 79 ++++ .../Microsoft/Teams/MicrosoftTeams.node.ts | 217 ++++++++++ .../nodes/Microsoft/Teams/teams.png | Bin 0 -> 6376 bytes packages/nodes-base/package.json | 2 + 8 files changed, 924 insertions(+), 2 deletions(-) create mode 100644 packages/nodes-base/credentials/MicrosoftTeamsOAuth2Api.credentials.ts create mode 100644 packages/nodes-base/nodes/Microsoft/Teams/ChannelDescription.ts create mode 100644 packages/nodes-base/nodes/Microsoft/Teams/ChannelMessageDescription.ts create mode 100644 packages/nodes-base/nodes/Microsoft/Teams/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Microsoft/Teams/MicrosoftTeams.node.ts create mode 100644 packages/nodes-base/nodes/Microsoft/Teams/teams.png diff --git a/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts index ad5770199..08990b2de 100644 --- a/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/MicrosoftOAuth2Api.credentials.ts @@ -11,17 +11,19 @@ export class MicrosoftOAuth2Api implements ICredentialType { displayName = 'Microsoft OAuth2 API'; documentationUrl = 'microsoft'; properties = [ + //info about the tenantID + //https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols#endpoints { displayName: 'Authorization URL', name: 'authUrl', type: 'string' as NodePropertyTypes, - default: 'https://login.microsoftonline.com/{yourtenantid}/oauth2/v2.0/authorize', + default: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', }, { displayName: 'Access Token URL', name: 'accessTokenUrl', type: 'string' as NodePropertyTypes, - default: 'https://login.microsoftonline.com/{yourtenantid}/oauth2/v2.0/token', + default: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', }, { displayName: 'Auth URI Query Parameters', diff --git a/packages/nodes-base/credentials/MicrosoftTeamsOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MicrosoftTeamsOAuth2Api.credentials.ts new file mode 100644 index 000000000..7b1d9da3d --- /dev/null +++ b/packages/nodes-base/credentials/MicrosoftTeamsOAuth2Api.credentials.ts @@ -0,0 +1,21 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class MicrosoftTeamsOAuth2Api implements ICredentialType { + name = 'microsoftTeamsOAuth2Api'; + extends = [ + 'microsoftOAuth2Api', + ]; + displayName = 'Microsoft OAuth2 API'; + properties = [ + //https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: 'openid offline_access User.ReadWrite.All Group.ReadWrite.All', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Microsoft/Teams/ChannelDescription.ts b/packages/nodes-base/nodes/Microsoft/Teams/ChannelDescription.ts new file mode 100644 index 000000000..5509c5e87 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Teams/ChannelDescription.ts @@ -0,0 +1,381 @@ +import { + INodeProperties, + } from 'n8n-workflow'; + +export const channelOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'channel', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a channel', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a channel', + }, + { + name: 'Get', + value: 'get', + description: 'Get a channel', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all channels', + }, + { + name: 'Update', + value: 'update', + description: 'Update a channel', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const channelFields = [ + +/* -------------------------------------------------------------------------- */ +/* channel:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Team ID', + name: 'teamId', + required: true, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTeams', + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'channel', + ], + }, + }, + default: '', + description: 'Team ID', + }, + { + displayName: 'Name', + name: 'name', + required: true, + type: 'string', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'channel', + ], + }, + }, + default: '', + description: 'Channel name as it will appear to the user in Microsoft Teams.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'channel', + ], + }, + }, + default: {}, + placeholder: 'Add Field', + options: [ + { + displayName: 'Description', + name: 'description', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: `channel's description`, + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Private', + value: 'private', + }, + { + name: 'Standard', + value: 'standard', + }, + ], + default: 'standard', + description: 'The type of the channel', + }, + ], + }, +/* -------------------------------------------------------------------------- */ +/* channel:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Team ID', + name: 'teamId', + required: true, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTeams', + }, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'channel', + ], + }, + }, + default: '', + description: 'Team ID', + }, + { + displayName: 'Channel ID', + name: 'channelId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getChannels', + loadOptionsDependsOn: [ + 'teamId', + ], + }, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'channel', + ], + }, + }, + default: '', + description: 'channel ID', + }, +/* -------------------------------------------------------------------------- */ +/* channel:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Team ID', + name: 'teamId', + required: true, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTeams', + }, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'channel', + ], + }, + }, + default: '', + description: 'Team ID', + }, + { + displayName: 'Channel ID', + name: 'channelId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getChannels', + loadOptionsDependsOn: [ + 'teamId', + ], + }, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'channel', + ], + }, + }, + default: '', + description: 'channel ID', + }, +/* -------------------------------------------------------------------------- */ +/* channel:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Team ID', + name: 'teamId', + required: true, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTeams', + }, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'channel', + ], + }, + }, + default: '', + description: 'Team ID', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'channel', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'channel', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, +/* -------------------------------------------------------------------------- */ +/* channel:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Team ID', + name: 'teamId', + required: true, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTeams', + }, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'channel', + ], + }, + }, + default: '', + description: 'Team ID', + }, + { + displayName: 'Channel ID', + name: 'channelId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getChannels', + loadOptionsDependsOn: [ + 'teamId', + ], + }, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'channel', + ], + }, + }, + default: '', + description: 'Channel ID', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'channel', + ], + }, + }, + default: {}, + placeholder: 'Add Field', + options: [ + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Channel name as it will appear to the user in Microsoft Teams.', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: `channel's description`, + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/Teams/ChannelMessageDescription.ts b/packages/nodes-base/nodes/Microsoft/Teams/ChannelMessageDescription.ts new file mode 100644 index 000000000..dead663ee --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Teams/ChannelMessageDescription.ts @@ -0,0 +1,220 @@ +import { + INodeProperties, + } from 'n8n-workflow'; + +export const channelMessageOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'channelMessage', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a message', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all messages', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const channelMessageFields = [ + +/* -------------------------------------------------------------------------- */ +/* channelMessage:create */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Team ID', + name: 'teamId', + required: true, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTeams', + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'channelMessage', + ], + }, + }, + default: '', + description: 'Team ID', + }, + { + displayName: 'Channel ID', + name: 'channelId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getChannels', + loadOptionsDependsOn: [ + 'teamId', + ], + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'channelMessage', + ], + }, + }, + default: '', + description: 'Channel ID', + }, + { + displayName: 'Message Type', + name: 'messageType', + required: true, + type: 'options', + options: [ + { + name: 'Text', + value: 'text', + }, + { + name: 'HTML', + value: 'html', + }, + ], + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'channelMessage', + ], + }, + }, + default: '', + description: 'The type of the content', + }, + { + displayName: 'Message', + name: 'message', + required: true, + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'channelMessage', + ], + }, + }, + default: '', + description: 'The content of the item.', + }, +/* -------------------------------------------------------------------------- */ +/* channelMessage:getAll */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Team ID', + name: 'teamId', + required: true, + type: 'options', + typeOptions: { + loadOptionsMethod: 'getTeams', + }, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'channelMessage', + ], + }, + }, + default: '', + description: 'Team ID', + }, + { + displayName: 'Channel ID', + name: 'channelId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getChannels', + loadOptionsDependsOn: [ + 'teamId', + ], + }, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'channelMessage', + ], + }, + }, + default: '', + description: 'Channel ID', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'channelMessage', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'channelMessage', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 500, + }, + default: 100, + description: 'How many results to return.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Microsoft/Teams/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/Teams/GenericFunctions.ts new file mode 100644 index 000000000..01ff3b964 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Teams/GenericFunctions.ts @@ -0,0 +1,79 @@ +import { + OptionsWithUri, + } from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +export async function microsoftApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, headers: IDataObject = {}): Promise { // tslint:disable-line:no-any + const options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + }, + method, + body, + qs, + uri: uri || `https://graph.microsoft.com${resource}`, + json: true + }; + try { + if (Object.keys(headers).length !== 0) { + options.headers = Object.assign({}, options.headers, headers); + } + //@ts-ignore + return await this.helpers.requestOAuth2.call(this, 'microsoftTeamsOAuth2Api', options); + } catch (error) { + if (error.response && error.response.body && error.response.body.error && error.response.body.error.message) { + // Try to return the error prettier + throw new Error(`Microsoft error response [${error.statusCode}]: ${error.response.body.error.message}`); + } + throw error; + } +} + +export async function microsoftApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + let uri: string | undefined; + + do { + responseData = await microsoftApiRequest.call(this, method, endpoint, body, query, uri); + uri = responseData['@odata.nextLink']; + returnData.push.apply(returnData, responseData[propertyName]); + if (query.limit && query.limit <= returnData.length) { + return returnData; + } + } while ( + responseData['@odata.nextLink'] !== undefined + ); + + return returnData; +} + +export async function microsoftApiRequestAllItemsSkip(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + query['$top'] = 100; + query['$skip'] = 0; + + do { + responseData = await microsoftApiRequest.call(this, method, endpoint, body, query); + query['$skip'] += query['$top']; + returnData.push.apply(returnData, responseData[propertyName]); + } while ( + responseData['value'].length !== 0 + ); + + return returnData; +} diff --git a/packages/nodes-base/nodes/Microsoft/Teams/MicrosoftTeams.node.ts b/packages/nodes-base/nodes/Microsoft/Teams/MicrosoftTeams.node.ts new file mode 100644 index 000000000..b14142e48 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/Teams/MicrosoftTeams.node.ts @@ -0,0 +1,217 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodePropertyOptions, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + microsoftApiRequest, + microsoftApiRequestAllItems, +} from './GenericFunctions'; + +import { + channelFields, + channelOperations, +} from './ChannelDescription'; + +import { + channelMessageFields, + channelMessageOperations, +} from './ChannelMessageDescription'; + +export class MicrosoftTeams implements INodeType { + description: INodeTypeDescription = { + displayName: 'Microsoft Teams', + name: 'microsoftTeams', + icon: 'file:teams.png', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Microsoft Teams API', + defaults: { + name: 'Microsoft Teams', + color: '#555cc7', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'microsoftTeamsOAuth2Api', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Channel', + value: 'channel', + }, + { + name: 'Channel Message (Beta)', + value: 'channelMessage', + }, + ], + default: 'channel', + description: 'The resource to operate on.', + }, + // CHANNEL + ...channelOperations, + ...channelFields, + /// MESSAGE + ...channelMessageOperations, + ...channelMessageFields, + ], + }; + + methods = { + loadOptions: { + // Get all the team's channels to display them to user so that he can + // select them easily + async getChannels(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const teamId = this.getCurrentNodeParameter('teamId') as string; + const { value } = await microsoftApiRequest.call(this, 'GET', `/v1.0/teams/${teamId}/channels`); + for (const channel of value) { + const channelName = channel.displayName; + const channelId = channel.id; + returnData.push({ + name: channelName, + value: channelId, + }); + } + return returnData; + }, + // Get all the teams to display them to user so that he can + // select them easily + async getTeams(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const { value } = await microsoftApiRequest.call(this, 'GET', '/v1.0/me/joinedTeams'); + for (const team of value) { + const teamName = team.displayName; + const teamId = team.id; + returnData.push({ + name: teamName, + value: teamId, + }); + } + return returnData; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + const qs: IDataObject = {}; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + if (resource === 'channel') { + //https://docs.microsoft.com/en-us/graph/api/channel-post?view=graph-rest-beta&tabs=http + if (operation === 'create') { + const teamId = this.getNodeParameter('teamId', i) as string; + const name = this.getNodeParameter('name', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + const body: IDataObject = { + displayName: name, + }; + if (options.description) { + body.description = options.description as string; + } + if (options.type) { + body.membershipType = options.type as string; + } + responseData = await microsoftApiRequest.call(this, 'POST', `/v1.0/teams/${teamId}/channels`, body); + } + //https://docs.microsoft.com/en-us/graph/api/channel-delete?view=graph-rest-beta&tabs=http + if (operation === 'delete') { + const teamId = this.getNodeParameter('teamId', i) as string; + const channelId = this.getNodeParameter('channelId', i) as string; + responseData = await microsoftApiRequest.call(this, 'DELETE', `/v1.0/teams/${teamId}/channels/${channelId}`); + responseData = { success: true }; + } + //https://docs.microsoft.com/en-us/graph/api/channel-get?view=graph-rest-beta&tabs=http + if (operation === 'get') { + const teamId = this.getNodeParameter('teamId', i) as string; + const channelId = this.getNodeParameter('channelId', i) as string; + responseData = await microsoftApiRequest.call(this, 'GET', `/v1.0/teams/${teamId}/channels/${channelId}`); + } + //https://docs.microsoft.com/en-us/graph/api/channel-list?view=graph-rest-beta&tabs=http + if (operation === 'getAll') { + const teamId = this.getNodeParameter('teamId', i) as string; + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + if (returnAll) { + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/v1.0/teams/${teamId}/channels`); + } else { + qs.limit = this.getNodeParameter('limit', 0) as number; + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/v1.0/teams/${teamId}/channels`, {}); + responseData = responseData.splice(0, qs.limit); + } + } + //https://docs.microsoft.com/en-us/graph/api/channel-patch?view=graph-rest-beta&tabs=http + if (operation === 'update') { + const teamId = this.getNodeParameter('teamId', i) as string; + const channelId = this.getNodeParameter('channelId', i) as string; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const body: IDataObject = {}; + if (updateFields.name) { + body.displayName = updateFields.name as string; + } + if (updateFields.description) { + body.description = updateFields.description as string; + } + responseData = await microsoftApiRequest.call(this, 'PATCH', `/v1.0/teams/${teamId}/channels/${channelId}`, body); + responseData = { success: true }; + } + } + if (resource === 'channelMessage') { + //https://docs.microsoft.com/en-us/graph/api/channel-post-messages?view=graph-rest-beta&tabs=http + if (operation === 'create') { + const teamId = this.getNodeParameter('teamId', i) as string; + const channelId = this.getNodeParameter('channelId', i) as string; + const messageType = this.getNodeParameter('messageType', i) as string; + const message = this.getNodeParameter('message', i) as string; + const body: IDataObject = { + body: { + contentType: messageType, + content: message, + } + }; + responseData = await microsoftApiRequest.call(this, 'POST', `/beta/teams/${teamId}/channels/${channelId}/messages`, body); + } + //https://docs.microsoft.com/en-us/graph/api/channel-list-messages?view=graph-rest-beta&tabs=http + if (operation === 'getAll') { + const teamId = this.getNodeParameter('teamId', i) as string; + const channelId = this.getNodeParameter('channelId', i) as string; + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + if (returnAll) { + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/beta/teams/${teamId}/channels/${channelId}/messages`); + } else { + qs.limit = this.getNodeParameter('limit', 0) as number; + responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', `/beta/teams/${teamId}/channels/${channelId}/messages`, {}); + responseData = responseData.splice(0, qs.limit); + } + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Microsoft/Teams/teams.png b/packages/nodes-base/nodes/Microsoft/Teams/teams.png new file mode 100644 index 0000000000000000000000000000000000000000..89dcee67ffb78902c8479daeb317c871852aa53c GIT binary patch literal 6376 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 literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 72caf8ea7..7023b26bb 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -113,6 +113,7 @@ "dist/credentials/MicrosoftOAuth2Api.credentials.js", "dist/credentials/MicrosoftOneDriveOAuth2Api.credentials.js", "dist/credentials/MicrosoftSql.credentials.js", + "dist/credentials/MicrosoftTeamsOAuth2Api.credentials.js", "dist/credentials/MoceanApi.credentials.js", "dist/credentials/MondayComApi.credentials.js", "dist/credentials/MongoDb.credentials.js", @@ -289,6 +290,7 @@ "dist/nodes/Microsoft/Excel/MicrosoftExcel.node.js", "dist/nodes/Microsoft/OneDrive/MicrosoftOneDrive.node.js", "dist/nodes/Microsoft/Sql/MicrosoftSql.node.js", + "dist/nodes/Microsoft/Teams/MicrosoftTeams.node.js", "dist/nodes/MoveBinaryData.node.js", "dist/nodes/Mocean/Mocean.node.js", "dist/nodes/MondayCom/MondayCom.node.js",