mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 20:54:07 -08:00
ffa7bba6cf
* ✨ Cisco Webex Node & Trigger * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Minor improvements Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
658 lines
13 KiB
TypeScript
658 lines
13 KiB
TypeScript
import {
|
|
OptionsWithUri,
|
|
} from 'request';
|
|
|
|
import {
|
|
IExecuteFunctions,
|
|
IHookFunctions,
|
|
ILoadOptionsFunctions,
|
|
} from 'n8n-core';
|
|
|
|
import {
|
|
ICredentialDataDecryptedObject,
|
|
IDataObject,
|
|
INodeProperties,
|
|
IWebhookFunctions,
|
|
NodeApiError,
|
|
} from 'n8n-workflow';
|
|
|
|
import {
|
|
upperFirst,
|
|
} from 'lodash';
|
|
|
|
import {
|
|
createHash,
|
|
} from 'crypto';
|
|
|
|
export async function webexApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
|
let options: OptionsWithUri = {
|
|
method,
|
|
body,
|
|
qs,
|
|
uri: uri || `https://webexapis.com/v1${resource}`,
|
|
json: true,
|
|
};
|
|
try {
|
|
if (Object.keys(option).length !== 0) {
|
|
options = Object.assign({}, options, option);
|
|
}
|
|
if (Object.keys(body).length === 0) {
|
|
delete options.body;
|
|
}
|
|
if (Object.keys(qs).length === 0) {
|
|
delete options.qs;
|
|
}
|
|
//@ts-ignore
|
|
return await this.helpers.requestOAuth2.call(this, 'ciscoWebexOAuth2Api', options, { tokenType: 'Bearer' });
|
|
} catch (error) {
|
|
throw new NodeApiError(this.getNode(), error);
|
|
}
|
|
}
|
|
|
|
export async function webexApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, options: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
|
|
|
const returnData: IDataObject[] = [];
|
|
|
|
let responseData;
|
|
let uri: string | undefined;
|
|
query.max = 100;
|
|
do {
|
|
responseData = await webexApiRequest.call(this, method, endpoint, body, query, uri, { resolveWithFullResponse: true, ...options });
|
|
if (responseData.headers.link) {
|
|
uri = responseData.headers['link'].split(';')[0].replace('<', '').replace('>', '');
|
|
}
|
|
returnData.push.apply(returnData, responseData.body[propertyName]);
|
|
} while (
|
|
responseData.headers['link'] !== undefined &&
|
|
responseData.headers['link'].includes('rel="next"')
|
|
);
|
|
return returnData;
|
|
}
|
|
|
|
export function getEvents() {
|
|
const resourceEvents: { [key: string]: string[] } = {
|
|
'attachmentAction': ['created', 'deleted', 'updated', '*'],
|
|
'membership': ['created', 'deleted', 'updated', '*'],
|
|
'message': ['created', 'deleted', 'updated', '*'],
|
|
'room': ['created', 'deleted', 'updated', '*'],
|
|
'meeting': ['created', 'deleted', 'updated', 'started', 'ended', '*'],
|
|
'recording': ['created', 'deleted', 'updated', '*'],
|
|
'telephonyCall': ['created', 'deleted', 'updated'],
|
|
'*': ['created', 'updated', 'deleted', '*'],
|
|
};
|
|
|
|
const elements: INodeProperties[] = [];
|
|
|
|
for (const resource of Object.keys(resourceEvents)) {
|
|
elements.push({
|
|
displayName: 'Event',
|
|
name: 'event',
|
|
type: 'options',
|
|
displayOptions: {
|
|
show: {
|
|
resource: [
|
|
(resource === '*') ? 'all' : resource,
|
|
],
|
|
},
|
|
},
|
|
options: resourceEvents[resource].map((event) => ({ value: (event === '*' ? 'all' : event), name: upperFirst(event) })),
|
|
default: '',
|
|
required: true,
|
|
});
|
|
}
|
|
return elements;
|
|
}
|
|
|
|
export function mapResource(event: string) {
|
|
return ({
|
|
'attachmentAction': 'attachmentActions',
|
|
'membership': 'memberships',
|
|
'message': 'messages',
|
|
'room': 'rooms',
|
|
'meeting': 'meetings',
|
|
'recording': 'recordings',
|
|
'telephonyCall': 'telephony_calls',
|
|
'all': 'all',
|
|
} as { [key: string]: string })[event];
|
|
}
|
|
|
|
export function getAttachemnts(attachements: IDataObject[]) {
|
|
const _attachments: IDataObject[] = [];
|
|
for (const attachment of attachements) {
|
|
const body: IDataObject[] = [];
|
|
const actions: IDataObject[] = [];
|
|
for (const element of (attachment?.elementsUi as IDataObject).elementValues as IDataObject[] || []) {
|
|
// tslint:disable-next-line: no-any
|
|
const { type, ...rest } = element as { type: string, [key: string]: any };
|
|
if (type.startsWith('input')) {
|
|
body.push({ type: `Input.${upperFirst(type.replace('input', ''))}`, ...removeEmptyProperties(rest) });
|
|
} else {
|
|
body.push({ type: upperFirst(type), ...removeEmptyProperties(rest) });
|
|
}
|
|
}
|
|
for (const action of (attachment?.actionsUi as IDataObject).actionValues as IDataObject[] || []) {
|
|
// tslint:disable-next-line: no-any
|
|
const { type, ...rest } = action as { type: string, [key: string]: any };
|
|
actions.push({ type: `Action.${upperFirst(type)}`, ...removeEmptyProperties(rest) });
|
|
}
|
|
_attachments.push({
|
|
contentType: 'application/vnd.microsoft.card.adaptive',
|
|
content: {
|
|
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
|
|
type: 'AdaptiveCard',
|
|
version: '1.2',
|
|
body,
|
|
actions,
|
|
},
|
|
});
|
|
}
|
|
return _attachments;
|
|
}
|
|
|
|
export function getActionInheritedProperties() {
|
|
return [
|
|
{
|
|
displayName: 'Title',
|
|
name: 'title',
|
|
type: 'string',
|
|
default: '',
|
|
required: true,
|
|
description: 'Label for button or link that represents this action.',
|
|
},
|
|
{
|
|
displayName: 'Icon URL',
|
|
name: 'iconUrl',
|
|
type: 'string',
|
|
default: '',
|
|
description: 'Optional icon to be shown on the action in conjunction with the title. Supports data URI in version 1.2+',
|
|
},
|
|
{
|
|
displayName: 'Style',
|
|
name: 'style',
|
|
type: 'options',
|
|
options: [
|
|
{
|
|
name: 'Default',
|
|
value: 'default',
|
|
},
|
|
{
|
|
name: 'Positive',
|
|
value: 'positive',
|
|
},
|
|
{
|
|
name: 'Destructive',
|
|
value: 'destructive',
|
|
},
|
|
],
|
|
default: 'default',
|
|
description: 'Controls the style of an Action, which influences how the action is displayed, spoken, etc.',
|
|
},
|
|
];
|
|
}
|
|
|
|
export function getTextBlockProperties() {
|
|
return [
|
|
{
|
|
displayName: 'Text',
|
|
name: 'text',
|
|
type: 'string',
|
|
default: '',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'textBlock',
|
|
],
|
|
},
|
|
},
|
|
required: true,
|
|
description: 'Text to display. A subset of markdown is supported (https://aka.ms/ACTextFeatures)',
|
|
},
|
|
{
|
|
displayName: 'Color',
|
|
name: 'color',
|
|
type: 'options',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'textBlock',
|
|
],
|
|
},
|
|
},
|
|
options: [
|
|
{
|
|
name: 'Default',
|
|
value: 'default',
|
|
},
|
|
{
|
|
name: 'Dark',
|
|
value: 'dark',
|
|
},
|
|
{
|
|
name: 'Light',
|
|
value: 'light',
|
|
},
|
|
{
|
|
name: 'Accent',
|
|
value: 'accent',
|
|
},
|
|
{
|
|
name: 'Good',
|
|
value: 'good',
|
|
},
|
|
{
|
|
name: 'Warning',
|
|
value: 'warning',
|
|
},
|
|
{
|
|
name: 'Attention',
|
|
value: 'attention',
|
|
},
|
|
],
|
|
default: 'default',
|
|
description: 'Color of the TextBlock element',
|
|
},
|
|
{
|
|
displayName: 'Font Type',
|
|
name: 'fontType',
|
|
type: 'options',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'textBlock',
|
|
],
|
|
},
|
|
},
|
|
options: [
|
|
{
|
|
name: 'Default',
|
|
value: 'default',
|
|
},
|
|
{
|
|
name: 'Monospace',
|
|
value: 'monospace',
|
|
},
|
|
],
|
|
default: 'default',
|
|
description: 'Type of font to use for rendering',
|
|
},
|
|
{
|
|
displayName: 'Horizontal Alignment',
|
|
name: 'horizontalAlignment',
|
|
type: 'options',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'textBlock',
|
|
],
|
|
},
|
|
},
|
|
options: [
|
|
{
|
|
name: 'Left',
|
|
value: 'left',
|
|
},
|
|
{
|
|
name: 'Center',
|
|
value: 'center',
|
|
},
|
|
{
|
|
name: 'Right',
|
|
value: 'right',
|
|
},
|
|
],
|
|
default: 'left',
|
|
description: 'Controls the horizontal text alignment',
|
|
},
|
|
{
|
|
displayName: 'Is Subtle',
|
|
name: 'isSubtle',
|
|
type: 'boolean',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'textBlock',
|
|
],
|
|
},
|
|
},
|
|
default: false,
|
|
description: 'Displays text slightly toned down to appear less prominent',
|
|
},
|
|
{
|
|
displayName: 'Max Lines',
|
|
name: 'maxLines',
|
|
type: 'number',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'textBlock',
|
|
],
|
|
},
|
|
},
|
|
default: 1,
|
|
description: 'Specifies the maximum number of lines to display',
|
|
},
|
|
{
|
|
displayName: 'Size',
|
|
name: 'size',
|
|
type: 'options',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'textBlock',
|
|
],
|
|
},
|
|
},
|
|
options: [
|
|
{
|
|
name: 'Default',
|
|
value: 'default',
|
|
},
|
|
{
|
|
name: 'Small',
|
|
value: 'small',
|
|
},
|
|
{
|
|
name: 'Medium',
|
|
value: 'medium',
|
|
},
|
|
{
|
|
name: 'Large',
|
|
value: 'large',
|
|
},
|
|
{
|
|
name: 'Extra Large',
|
|
value: 'extraLarge',
|
|
},
|
|
],
|
|
default: 'default',
|
|
description: 'Controls size of text',
|
|
},
|
|
{
|
|
displayName: 'Weight',
|
|
name: 'weight',
|
|
type: 'options',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'textBlock',
|
|
],
|
|
},
|
|
},
|
|
options: [
|
|
{
|
|
name: 'Default',
|
|
value: 'default',
|
|
},
|
|
{
|
|
name: 'Lighter',
|
|
value: 'lighter',
|
|
},
|
|
{
|
|
name: 'Bolder',
|
|
value: 'bolder',
|
|
},
|
|
],
|
|
default: 'default',
|
|
description: 'Controls the weight of TextBlock elements',
|
|
},
|
|
{
|
|
displayName: 'Wrap',
|
|
name: 'wrap',
|
|
type: 'boolean',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'textBlock',
|
|
],
|
|
},
|
|
},
|
|
default: true,
|
|
description: 'If true, allow text to wrap. Otherwise, text is clipped',
|
|
},
|
|
{
|
|
displayName: 'Height',
|
|
name: 'height',
|
|
type: 'options',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'textBlock',
|
|
],
|
|
},
|
|
},
|
|
options: [
|
|
{
|
|
name: 'Auto',
|
|
value: 'auto',
|
|
},
|
|
{
|
|
name: 'Stretch',
|
|
value: 'stretch',
|
|
},
|
|
],
|
|
default: 'auto',
|
|
description: 'Specifies the height of the element',
|
|
},
|
|
{
|
|
displayName: 'Separator',
|
|
name: 'separator',
|
|
type: 'boolean',
|
|
default: false,
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'textBlock',
|
|
],
|
|
},
|
|
},
|
|
description: 'When true, draw a separating line at the top of the element.',
|
|
},
|
|
{
|
|
displayName: 'Spacing',
|
|
name: 'spacing',
|
|
type: 'options',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'textBlock',
|
|
],
|
|
},
|
|
},
|
|
options: [
|
|
{
|
|
name: 'Default',
|
|
value: 'default',
|
|
},
|
|
{
|
|
name: 'None',
|
|
value: 'none',
|
|
},
|
|
{
|
|
name: 'Small',
|
|
value: 'small',
|
|
},
|
|
{
|
|
name: 'Medium',
|
|
value: 'medium',
|
|
},
|
|
{
|
|
name: 'Large',
|
|
value: 'large',
|
|
},
|
|
{
|
|
name: 'Extra Large',
|
|
value: 'extraLarge',
|
|
},
|
|
{
|
|
name: 'Padding',
|
|
value: 'padding',
|
|
},
|
|
],
|
|
default: 'default',
|
|
description: 'Controls the amount of spacing between this element and the preceding element',
|
|
},
|
|
{
|
|
displayName: 'ID',
|
|
name: 'id',
|
|
type: 'string',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'textBlock',
|
|
],
|
|
},
|
|
},
|
|
default: '',
|
|
description: 'A unique identifier associated with the item',
|
|
},
|
|
{
|
|
displayName: 'Is Visible',
|
|
name: 'isVisible',
|
|
type: 'boolean',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'textBlock',
|
|
],
|
|
},
|
|
},
|
|
default: true,
|
|
description: 'If false, this item will be removed from the visual trees',
|
|
},
|
|
];
|
|
}
|
|
|
|
export function getInputTextProperties() {
|
|
return [
|
|
{
|
|
displayName: 'ID',
|
|
name: 'id',
|
|
type: 'string',
|
|
required: true,
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'inputText',
|
|
],
|
|
},
|
|
},
|
|
default: '',
|
|
description: 'Unique identifier for the value. Used to identify collected input when the Submit action is performed',
|
|
},
|
|
{
|
|
displayName: 'Is Multiline',
|
|
name: 'isMultiline',
|
|
type: 'boolean',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'inputText',
|
|
],
|
|
},
|
|
},
|
|
default: false,
|
|
description: 'If true, allow multiple lines of input',
|
|
},
|
|
{
|
|
displayName: 'Max Length',
|
|
name: 'maxLength',
|
|
type: 'number',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'inputText',
|
|
],
|
|
},
|
|
},
|
|
default: 1,
|
|
description: 'Hint of maximum length characters to collect (may be ignored by some clients)',
|
|
},
|
|
{
|
|
displayName: 'Placeholder',
|
|
name: 'placeholder',
|
|
type: 'string',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'inputText',
|
|
],
|
|
},
|
|
},
|
|
default: '',
|
|
description: 'Description of the input desired. Displayed when no text has been input',
|
|
},
|
|
{
|
|
displayName: 'Regex',
|
|
name: 'regex',
|
|
type: 'string',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'inputText',
|
|
],
|
|
},
|
|
},
|
|
default: '',
|
|
description: 'Regular expression indicating the required format of this text input',
|
|
},
|
|
{
|
|
displayName: 'Style',
|
|
name: 'style',
|
|
type: 'options',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'inputText',
|
|
],
|
|
},
|
|
},
|
|
options: [
|
|
{
|
|
name: 'Text',
|
|
value: 'text',
|
|
},
|
|
{
|
|
name: 'Tel',
|
|
value: 'tel',
|
|
},
|
|
{
|
|
name: 'URL',
|
|
value: 'url',
|
|
},
|
|
{
|
|
name: 'Email',
|
|
value: 'email',
|
|
},
|
|
],
|
|
default: 'text',
|
|
description: 'Style hint for text input',
|
|
},
|
|
{
|
|
displayName: 'Value',
|
|
name: 'value',
|
|
type: 'string',
|
|
displayOptions: {
|
|
show: {
|
|
type: [
|
|
'inputText',
|
|
],
|
|
},
|
|
},
|
|
default: '',
|
|
description: 'The initial value for this field',
|
|
},
|
|
];
|
|
}
|
|
|
|
// tslint:disable-next-line: no-any
|
|
function removeEmptyProperties(rest: { [key: string]: any }) {
|
|
return Object.keys(rest)
|
|
.filter((k) => rest[k] !== '')
|
|
.reduce((a, k) => ({ ...a, [k]: rest[k] }), {});
|
|
}
|
|
|
|
export function getAutomaticSecret(credentials: ICredentialDataDecryptedObject) {
|
|
const data = `${credentials.clientId},${credentials.clientSecret}`;
|
|
return createHash('md5').update(data).digest('hex');
|
|
}
|