Fix some issues with Slack-Node

This commit is contained in:
Jan Oberhauser 2020-03-15 15:51:49 +01:00
parent 150aa7daee
commit ff7f0a5de5
9 changed files with 126 additions and 54 deletions

View file

@ -2,6 +2,34 @@
This list shows all the versions which include breaking changes and how to upgrade
## ???
### What changed?
To make it easier to use the data which the Slack-Node outputs we no longer return the whole
object the Slack-API returns if the only other property is `"ok": true`. In this case it returns
now directly the data under "channel".
### When is action necessary?
When you currently use the Slack-Node with Operations Channel -> Create and you use
any of the data the node outputs.
### How to upgrade:
All values that get referenced which were before under the property "channel" are now on the main level.
This means that these expressions have to get adjusted.
Meaning if the expression used before was:
```
{{ $node["Slack"].data["channel"]["id"] }}
```
it has to get changed to:
```
{{ $node["Slack"].data["id"] }}
```
## 0.37.0
### What changed?

View file

@ -203,7 +203,7 @@ class App {
});
}
jwt.verify(token, getKey, {}, (err: Error) => {
jwt.verify(token, getKey, {}, (err: Error, decoded: object) => {
if (err) return ResponseHelper.jwtAuthAuthorizationError(res, "Invalid token");
next();

View file

@ -115,7 +115,7 @@ export async function prepareBinaryData(binaryData: Buffer, filePath?: string, m
* @param {IWorkflowExecuteAdditionalData} additionalData
* @returns
*/
export function requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, node: INode, additionalData: IWorkflowExecuteAdditionalData, tokenType?: string | undefined, property?: string) {
export function requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, node: INode, additionalData: IWorkflowExecuteAdditionalData, tokenType?: string, property?: string) {
const credentials = this.getCredentials(credentialsType) as ICredentialDataDecryptedObject;
if (credentials === undefined) {
@ -134,7 +134,7 @@ export function requestOAuth(this: IAllExecuteFunctions, credentialsType: string
const oauthTokenData = credentials.oauthTokenData as clientOAuth2.Data;
const token = oAuthClient.createToken(get(oauthTokenData, property as string) || oauthTokenData.accessToken, oauthTokenData.refresToken, tokenType || oauthTokenData.tokenType, oauthTokenData);
const token = oAuthClient.createToken(get(oauthTokenData, property as string) || oauthTokenData.accessToken, oauthTokenData.refreshToken, tokenType || oauthTokenData.tokenType, oauthTokenData);
// Signs the request by adding authorization headers or query parameters depending
// on the token-type used.
const newRequestOptions = token.sign(requestOptions as clientOAuth2.RequestObject);
@ -412,7 +412,7 @@ export function getExecutePollFunctions(workflow: Workflow, node: INode, additio
helpers: {
prepareBinaryData,
request: requestPromise,
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType: string | undefined = undefined, property: string = ''): Promise<any> { // tslint:disable-line:no-any
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
},
returnJsonArray,
@ -466,7 +466,7 @@ export function getExecuteTriggerFunctions(workflow: Workflow, node: INode, addi
helpers: {
prepareBinaryData,
request: requestPromise,
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType: string | undefined = undefined, property: string = ''): Promise<any> { // tslint:disable-line:no-any
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
},
returnJsonArray,
@ -547,7 +547,7 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx
helpers: {
prepareBinaryData,
request: requestPromise,
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType: string | undefined = undefined, property: string = ''): Promise<any> { // tslint:disable-line:no-any
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
},
returnJsonArray,
@ -629,7 +629,7 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData:
helpers: {
prepareBinaryData,
request: requestPromise,
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType: string | undefined, property: string = ''): Promise<any> { // tslint:disable-line:no-any
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
},
},
@ -679,7 +679,7 @@ export function getLoadOptionsFunctions(workflow: Workflow, node: INode, additio
},
helpers: {
request: requestPromise,
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType: string | undefined = undefined, property: string = ''): Promise<any> { // tslint:disable-line:no-any
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
},
},
@ -737,7 +737,7 @@ export function getExecuteHookFunctions(workflow: Workflow, node: INode, additio
},
helpers: {
request: requestPromise,
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType: string | undefined = undefined, property: string = ''): Promise<any> { // tslint:disable-line:no-any
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
},
},
@ -822,7 +822,7 @@ export function getExecuteWebhookFunctions(workflow: Workflow, node: INode, addi
helpers: {
prepareBinaryData,
request: requestPromise,
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType: string | undefined = undefined, property: string = ''): Promise<any> { // tslint:disable-line:no-any
requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise<any> { // tslint:disable-line:no-any
return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property);
},
returnJsonArray,

View file

@ -12,7 +12,7 @@ const userScopes = [
'files:write',
'stars:read',
'stars:write',
]
];
export class SlackOAuth2Api implements ICredentialType {

View file

@ -234,7 +234,10 @@ export const channelFields = [
{
displayName: 'User ID',
name: 'userId',
type: 'string',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getUsers',
},
default: '',
displayOptions: {
show: {
@ -321,9 +324,12 @@ export const channelFields = [
description: 'The name of the channel to create.',
},
{
displayName: 'User ID',
displayName: 'User',
name: 'userId',
type: 'string',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getUsers',
},
displayOptions: {
show: {
operation: [

View file

@ -60,7 +60,7 @@ export async function slackApiRequest(this: IExecuteFunctions | IExecuteSingleFu
}
}
export async function salckApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
export async function slackApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const returnData: IDataObject[] = [];
let responseData;
query.page = 1;
@ -73,7 +73,7 @@ export async function salckApiRequestAllItems(this: IExecuteFunctions | ILoadOpt
} while (
(responseData.response_metadata !== undefined &&
responseData.response_metadata.mext_cursor !== undefined &&
responseData.response_metadata.next_cursor !== "" &&
responseData.response_metadata.next_cursor !== '' &&
responseData.response_metadata.next_cursor !== null) ||
(responseData.paging !== undefined &&
responseData.paging.pages !== undefined &&

View file

@ -43,7 +43,7 @@ export const messageFields = [
displayOptions: {
show: {
operation: [
'post'
'post',
],
resource: [
'message',
@ -64,7 +64,7 @@ export const messageFields = [
displayOptions: {
show: {
operation: [
'post'
'post',
],
resource: [
'message',
@ -80,6 +80,9 @@ export const messageFields = [
default: false,
displayOptions: {
show: {
authentication: [
'accessToken',
],
operation: [
'post'
],
@ -98,10 +101,10 @@ export const messageFields = [
displayOptions: {
show: {
as_user: [
false
false,
],
operation: [
'post'
'post',
],
resource: [
'message',
@ -121,7 +124,7 @@ export const messageFields = [
displayOptions: {
show: {
operation: [
'post'
'post',
],
resource: [
'message',
@ -411,9 +414,12 @@ export const messageFields = [
/* message:update */
/* ----------------------------------------------------------------------- */
{
displayName: 'Channel ID',
displayName: 'Channel',
name: 'channelId',
type: 'string',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getChannels',
},
required: true,
default: '',
displayOptions: {
@ -471,6 +477,9 @@ export const messageFields = [
default: false,
displayOptions: {
show: {
authentication: [
'accessToken',
],
operation: [
'update'
],

View file

@ -11,24 +11,24 @@ import {
INodePropertyOptions,
} from 'n8n-workflow';
import {
channelOperations,
channelFields,
channelOperations,
} from './ChannelDescription';
import {
messageOperations,
messageFields,
messageOperations,
} from './MessageDescription';
import {
starOperations,
starFields,
starOperations,
} from './StarDescription';
import {
fileOperations,
fileFields,
fileOperations,
} from './FileDescription';
import {
slackApiRequest,
salckApiRequestAllItems,
slackApiRequestAllItems,
} from './GenericFunctions';
import {
IAttachment,
@ -133,7 +133,7 @@ export class Slack implements INodeType {
// select them easily
async getUsers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const users = await salckApiRequestAllItems.call(this, 'members', 'GET', '/users.list');
const users = await slackApiRequestAllItems.call(this, 'members', 'GET', '/users.list');
for (const user of users) {
const userName = user.name;
const userId = user.id;
@ -142,13 +142,20 @@ export class Slack implements INodeType {
value: userId,
});
}
returnData.sort((a, b) => {
if (a.name < b.name) { return -1; }
if (a.name > b.name) { return 1; }
return 0;
});
return returnData;
},
// Get all the users to display them to user so that he can
// select them easily
async getChannels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const channels = await salckApiRequestAllItems.call(this, 'channels', 'GET', '/conversations.list');
const channels = await slackApiRequestAllItems.call(this, 'channels', 'GET', '/conversations.list');
for (const channel of channels) {
const channelName = channel.name;
const channelId = channel.id;
@ -157,6 +164,13 @@ export class Slack implements INodeType {
value: channelId,
});
}
returnData.sort((a, b) => {
if (a.name < b.name) { return -1; }
if (a.name > b.name) { return 1; }
return 0;
});
return returnData;
},
}
@ -168,10 +182,12 @@ export class Slack implements INodeType {
const length = items.length as unknown as number;
let qs: IDataObject;
let responseData;
const authentication = this.getNodeParameter('authentication', 0) as string;
const resource = this.getNodeParameter('resource', 0) as string;
const operation = this.getNodeParameter('operation', 0) as string;
for (let i = 0; i < length; i++) {
qs = {};
const resource = this.getNodeParameter('resource', 0) as string;
const operation = this.getNodeParameter('operation', 0) as string;
if (resource === 'channel') {
//https://api.slack.com/methods/conversations.archive
if (operation === 'archive') {
@ -203,6 +219,7 @@ export class Slack implements INodeType {
body.user_ids = (additionalFields.users as string[]).join(',');
}
responseData = await slackApiRequest.call(this, 'POST', '/conversations.create', body, qs);
responseData = responseData.channel;
}
//https://api.slack.com/methods/conversations.kick
if (operation === 'kick') {
@ -241,7 +258,7 @@ export class Slack implements INodeType {
qs.exclude_archived = filters.excludeArchived as boolean;
}
if (returnAll === true) {
responseData = await salckApiRequestAllItems.call(this, 'channels', 'GET', '/conversations.list', {}, qs);
responseData = await slackApiRequestAllItems.call(this, 'channels', 'GET', '/conversations.list', {}, qs);
} else {
qs.limit = this.getNodeParameter('limit', i) as number;
responseData = await slackApiRequest.call(this, 'GET', '/conversations.list', {}, qs);
@ -264,7 +281,7 @@ export class Slack implements INodeType {
qs.oldest = filters.oldest as string;
}
if (returnAll === true) {
responseData = await salckApiRequestAllItems.call(this, 'messages', 'GET', '/conversations.history', {}, qs);
responseData = await slackApiRequestAllItems.call(this, 'messages', 'GET', '/conversations.history', {}, qs);
} else {
qs.limit = this.getNodeParameter('limit', i) as number;
responseData = await slackApiRequest.call(this, 'GET', '/conversations.history', {}, qs);
@ -335,7 +352,7 @@ export class Slack implements INodeType {
qs.oldest = filters.oldest as string;
}
if (returnAll === true) {
responseData = await salckApiRequestAllItems.call(this, 'messages', 'GET', '/conversations.replies', {}, qs);
responseData = await slackApiRequestAllItems.call(this, 'messages', 'GET', '/conversations.replies', {}, qs);
} else {
qs.limit = this.getNodeParameter('limit', i) as number;
responseData = await slackApiRequest.call(this, 'GET', '/conversations.replies', {}, qs);
@ -379,15 +396,18 @@ export class Slack implements INodeType {
const channel = this.getNodeParameter('channel', i) as string;
const text = this.getNodeParameter('text', i) as string;
const attachments = this.getNodeParameter('attachments', i, []) as unknown as IAttachment[];
const as_user = this.getNodeParameter('as_user', i) as boolean;
const body: IDataObject = {
channel: channel,
channel,
text,
as_user,
};
if (as_user === false) {
if (authentication === 'accessToken') {
body.as_user = this.getNodeParameter('as_user', i) as boolean;
}
if (body.as_user === false) {
body.username = this.getNodeParameter('username', i) as string;
}
// The node does save the fields data differently than the API
// expects so fix the data befre we send the request
for (const attachment of attachments) {
@ -411,17 +431,20 @@ export class Slack implements INodeType {
}
//https://api.slack.com/methods/chat.update
if (operation === 'update') {
const channel = this.getNodeParameter('channel', i) as string;
const channel = this.getNodeParameter('channelId', i) as string;
const text = this.getNodeParameter('text', i) as string;
const ts = this.getNodeParameter('ts', i) as string;
const as_user = this.getNodeParameter('as_user', i) as boolean;
const attachments = this.getNodeParameter('attachments', i, []) as unknown as IAttachment[];
const body: IDataObject = {
channel,
text,
ts,
as_user,
};
if (authentication === 'accessToken') {
body.as_user = this.getNodeParameter('as_user', i) as boolean;
}
// The node does save the fields data differently than the API
// expects so fix the data befre we send the request
for (const attachment of attachments) {
@ -485,7 +508,7 @@ export class Slack implements INodeType {
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
if (returnAll === true) {
responseData = await salckApiRequestAllItems.call(this, 'items', 'GET', '/stars.list', {}, qs);
responseData = await slackApiRequestAllItems.call(this, 'items', 'GET', '/stars.list', {}, qs);
} else {
qs.limit = this.getNodeParameter('limit', i) as number;
responseData = await slackApiRequest.call(this, 'GET', '/stars.list', {}, qs);
@ -522,15 +545,15 @@ export class Slack implements INodeType {
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`);
}
body.file = {
//@ts-ignore
value: Buffer.from(items[i].binary[binaryPropertyName].data, BINARY_ENCODING),
options: {
//@ts-ignore
value: Buffer.from(items[i].binary[binaryPropertyName].data, BINARY_ENCODING),
options: {
//@ts-ignore
filename: items[i].binary[binaryPropertyName].fileName,
//@ts-ignore
contentType: items[i].binary[binaryPropertyName].mimeType,
}
filename: items[i].binary[binaryPropertyName].fileName,
//@ts-ignore
contentType: items[i].binary[binaryPropertyName].mimeType,
}
};
responseData = await slackApiRequest.call(this, 'POST', '/files.upload', {}, qs, { 'Content-Type': 'multipart/form-data' }, { formData: body });
responseData = responseData.file;
} else {
@ -563,7 +586,7 @@ export class Slack implements INodeType {
qs.user = filters.userId as string;
}
if (returnAll === true) {
responseData = await salckApiRequestAllItems.call(this, 'files', 'GET', '/files.list', {}, qs);
responseData = await slackApiRequestAllItems.call(this, 'files', 'GET', '/files.list', {}, qs);
} else {
qs.count = this.getNodeParameter('limit', i) as number;
responseData = await slackApiRequest.call(this, 'GET', '/files.list', {}, qs);

View file

@ -26,7 +26,7 @@ export const starOperations = [
{
name: 'Get All',
value: 'getAll',
description: 'Get all stars for a user.',
description: 'Get all stars of autenticated user.',
},
],
default: 'add',
@ -60,7 +60,10 @@ export const starFields = [
{
displayName: 'Channel ID',
name: 'channelId',
type: 'string',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getChannels',
},
default: '',
description: 'Channel to add star to, or channel where the message to add star to was posted (used with timestamp).',
},
@ -111,7 +114,10 @@ export const starFields = [
{
displayName: 'Channel ID',
name: 'channelId',
type: 'string',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getChannels',
},
default: '',
description: 'Channel to add star to, or channel where the message to add star to was posted (used with timestamp).',
},