mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-21 02:56:40 -08:00
feat(core): Add "Sent by n8n" attribution (#7183)
Github issue / Community forum post (link here to close automatically): --------- Co-authored-by: Giulio Andreini <g.andreini@gmail.com>
This commit is contained in:
parent
f0a66873b9
commit
8f9fe6269b
|
@ -143,7 +143,7 @@ import {
|
||||||
setWorkflowExecutionMetadata,
|
setWorkflowExecutionMetadata,
|
||||||
} from './WorkflowExecutionMetadata';
|
} from './WorkflowExecutionMetadata';
|
||||||
import { getSecretsProxy } from './Secrets';
|
import { getSecretsProxy } from './Secrets';
|
||||||
import { getUserN8nFolderPath } from './UserSettings';
|
import { getUserN8nFolderPath, getInstanceId } from './UserSettings';
|
||||||
import Container from 'typedi';
|
import Container from 'typedi';
|
||||||
import type { BinaryData } from './BinaryData/types';
|
import type { BinaryData } from './BinaryData/types';
|
||||||
|
|
||||||
|
@ -2506,6 +2506,7 @@ const getCommonWorkflowFunctions = (
|
||||||
|
|
||||||
getRestApiUrl: () => additionalData.restApiUrl,
|
getRestApiUrl: () => additionalData.restApiUrl,
|
||||||
getInstanceBaseUrl: () => additionalData.instanceBaseUrl,
|
getInstanceBaseUrl: () => additionalData.instanceBaseUrl,
|
||||||
|
getInstanceId: async () => getInstanceId(),
|
||||||
getTimezone: () => getTimezone(workflow, additionalData),
|
getTimezone: () => getTimezone(workflow, additionalData),
|
||||||
|
|
||||||
prepareOutputData: async (outputData) => [outputData],
|
prepareOutputData: async (outputData) => [outputData],
|
||||||
|
|
|
@ -103,6 +103,7 @@ export const FILTER_NODE_TYPE = 'n8n-nodes-base.filter';
|
||||||
export const FUNCTION_NODE_TYPE = 'n8n-nodes-base.function';
|
export const FUNCTION_NODE_TYPE = 'n8n-nodes-base.function';
|
||||||
export const GITHUB_TRIGGER_NODE_TYPE = 'n8n-nodes-base.githubTrigger';
|
export const GITHUB_TRIGGER_NODE_TYPE = 'n8n-nodes-base.githubTrigger';
|
||||||
export const GIT_NODE_TYPE = 'n8n-nodes-base.git';
|
export const GIT_NODE_TYPE = 'n8n-nodes-base.git';
|
||||||
|
export const GOOGLE_GMAIL_NODE_TYPE = 'n8n-nodes-base.gmail';
|
||||||
export const GOOGLE_SHEETS_NODE_TYPE = 'n8n-nodes-base.googleSheets';
|
export const GOOGLE_SHEETS_NODE_TYPE = 'n8n-nodes-base.googleSheets';
|
||||||
export const ERROR_TRIGGER_NODE_TYPE = 'n8n-nodes-base.errorTrigger';
|
export const ERROR_TRIGGER_NODE_TYPE = 'n8n-nodes-base.errorTrigger';
|
||||||
export const ELASTIC_SECURITY_NODE_TYPE = 'n8n-nodes-base.elasticSecurity';
|
export const ELASTIC_SECURITY_NODE_TYPE = 'n8n-nodes-base.elasticSecurity';
|
||||||
|
@ -136,6 +137,7 @@ export const SPREADSHEET_FILE_NODE_TYPE = 'n8n-nodes-base.spreadsheetFile';
|
||||||
export const SPLIT_IN_BATCHES_NODE_TYPE = 'n8n-nodes-base.splitInBatches';
|
export const SPLIT_IN_BATCHES_NODE_TYPE = 'n8n-nodes-base.splitInBatches';
|
||||||
export const START_NODE_TYPE = 'n8n-nodes-base.start';
|
export const START_NODE_TYPE = 'n8n-nodes-base.start';
|
||||||
export const SWITCH_NODE_TYPE = 'n8n-nodes-base.switch';
|
export const SWITCH_NODE_TYPE = 'n8n-nodes-base.switch';
|
||||||
|
export const TELEGRAM_NODE_TYPE = 'n8n-nodes-base.telegram';
|
||||||
export const THE_HIVE_TRIGGER_NODE_TYPE = 'n8n-nodes-base.theHiveTrigger';
|
export const THE_HIVE_TRIGGER_NODE_TYPE = 'n8n-nodes-base.theHiveTrigger';
|
||||||
export const QUICKBOOKS_NODE_TYPE = 'n8n-nodes-base.quickbooks';
|
export const QUICKBOOKS_NODE_TYPE = 'n8n-nodes-base.quickbooks';
|
||||||
export const WAIT_NODE_TYPE = 'n8n-nodes-base.wait';
|
export const WAIT_NODE_TYPE = 'n8n-nodes-base.wait';
|
||||||
|
@ -624,3 +626,5 @@ export const nonExistingJsonPath = '_!^&*';
|
||||||
export const ASK_AI_MAX_PROMPT_LENGTH = 600;
|
export const ASK_AI_MAX_PROMPT_LENGTH = 600;
|
||||||
export const ASK_AI_MIN_PROMPT_LENGTH = 15;
|
export const ASK_AI_MIN_PROMPT_LENGTH = 15;
|
||||||
export const ASK_AI_LOADING_DURATION_MS = 12000;
|
export const ASK_AI_LOADING_DURATION_MS = 12000;
|
||||||
|
|
||||||
|
export const APPEND_ATTRIBUTION_DEFAULT_PATH = 'parameters.options.appendAttribution';
|
||||||
|
|
|
@ -7,7 +7,12 @@ import type { IUserNodesPanelSession } from './telemetry.types';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useRootStore } from '@/stores/n8nRoot.store';
|
import { useRootStore } from '@/stores/n8nRoot.store';
|
||||||
import { useTelemetryStore } from '@/stores/telemetry.store';
|
import { useTelemetryStore } from '@/stores/telemetry.store';
|
||||||
import { SLACK_NODE_TYPE } from '@/constants';
|
import {
|
||||||
|
APPEND_ATTRIBUTION_DEFAULT_PATH,
|
||||||
|
MICROSOFT_TEAMS_NODE_TYPE,
|
||||||
|
SLACK_NODE_TYPE,
|
||||||
|
TELEGRAM_NODE_TYPE,
|
||||||
|
} from '@/constants';
|
||||||
import { usePostHog } from '@/stores/posthog.store';
|
import { usePostHog } from '@/stores/posthog.store';
|
||||||
import { useNDVStore } from '@/stores';
|
import { useNDVStore } from '@/stores';
|
||||||
|
|
||||||
|
@ -230,22 +235,21 @@ export class Telemetry {
|
||||||
// so we are using this method as centralized way to track node parameters changes
|
// so we are using this method as centralized way to track node parameters changes
|
||||||
trackNodeParametersValuesChange(nodeType: string, change: IUpdateInformation) {
|
trackNodeParametersValuesChange(nodeType: string, change: IUpdateInformation) {
|
||||||
if (this.rudderStack) {
|
if (this.rudderStack) {
|
||||||
switch (nodeType) {
|
const changeNameMap: { [key: string]: string } = {
|
||||||
case SLACK_NODE_TYPE:
|
[SLACK_NODE_TYPE]: 'parameters.otherOptions.includeLinkToWorkflow',
|
||||||
if (change.name === 'parameters.otherOptions.includeLinkToWorkflow') {
|
[MICROSOFT_TEAMS_NODE_TYPE]: 'parameters.options.includeLinkToWorkflow',
|
||||||
this.track(
|
[TELEGRAM_NODE_TYPE]: 'parameters.additionalFields.appendAttribution',
|
||||||
'User toggled n8n reference option',
|
};
|
||||||
{
|
const changeName = changeNameMap[nodeType] || APPEND_ATTRIBUTION_DEFAULT_PATH;
|
||||||
node: nodeType,
|
if (change.name === changeName) {
|
||||||
toValue: change.value,
|
this.track(
|
||||||
},
|
'User toggled n8n reference option',
|
||||||
{ withPostHog: true },
|
{
|
||||||
);
|
node: nodeType,
|
||||||
}
|
toValue: change.value,
|
||||||
break;
|
},
|
||||||
|
{ withPostHog: true },
|
||||||
default:
|
);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,13 +11,14 @@ export class EmailSend extends VersionedNodeType {
|
||||||
name: 'emailSend',
|
name: 'emailSend',
|
||||||
icon: 'fa:envelope',
|
icon: 'fa:envelope',
|
||||||
group: ['output'],
|
group: ['output'],
|
||||||
defaultVersion: 2,
|
defaultVersion: 2.1,
|
||||||
description: 'Sends an email using SMTP protocol',
|
description: 'Sends an email using SMTP protocol',
|
||||||
};
|
};
|
||||||
|
|
||||||
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
|
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
|
||||||
1: new EmailSendV1(baseDescription),
|
1: new EmailSendV1(baseDescription),
|
||||||
2: new EmailSendV2(baseDescription),
|
2: new EmailSendV2(baseDescription),
|
||||||
|
2.1: new EmailSendV2(baseDescription),
|
||||||
};
|
};
|
||||||
|
|
||||||
super(nodeVersions, baseDescription);
|
super(nodeVersions, baseDescription);
|
||||||
|
|
|
@ -15,7 +15,7 @@ const versionDescription: INodeTypeDescription = {
|
||||||
name: 'emailSend',
|
name: 'emailSend',
|
||||||
icon: 'fa:envelope',
|
icon: 'fa:envelope',
|
||||||
group: ['output'],
|
group: ['output'],
|
||||||
version: 2,
|
version: [2, 2.1],
|
||||||
description: 'Sends an email using SMTP protocol',
|
description: 'Sends an email using SMTP protocol',
|
||||||
defaults: {
|
defaults: {
|
||||||
name: 'Send Email',
|
name: 'Send Email',
|
||||||
|
|
|
@ -43,6 +43,31 @@ const properties: INodeProperties[] = [
|
||||||
placeholder: 'My subject line',
|
placeholder: 'My subject line',
|
||||||
description: 'Subject line of the email',
|
description: 'Subject line of the email',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Email Format',
|
||||||
|
name: 'emailFormat',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Text',
|
||||||
|
value: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'HTML',
|
||||||
|
value: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Both',
|
||||||
|
value: 'both',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'html',
|
||||||
|
displayOptions: {
|
||||||
|
hide: {
|
||||||
|
'@version': [2],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Email Format',
|
displayName: 'Email Format',
|
||||||
name: 'emailFormat',
|
name: 'emailFormat',
|
||||||
|
@ -62,6 +87,11 @@ const properties: INodeProperties[] = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
default: 'text',
|
default: 'text',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
'@version': [2],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Text',
|
displayName: 'Text',
|
||||||
|
@ -100,6 +130,15 @@ const properties: INodeProperties[] = [
|
||||||
placeholder: 'Add Option',
|
placeholder: 'Add Option',
|
||||||
default: {},
|
default: {},
|
||||||
options: [
|
options: [
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
|
displayName: 'Append n8n Attribution',
|
||||||
|
name: 'appendAttribution',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description:
|
||||||
|
'Whether to include the phrase “This email was sent automatically with n8n” to the end of the email',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Attachments',
|
displayName: 'Attachments',
|
||||||
name: 'attachments',
|
name: 'attachments',
|
||||||
|
@ -153,6 +192,7 @@ const displayOptions = {
|
||||||
export const description = updateDisplayOptions(displayOptions, properties);
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
type EmailSendOptions = {
|
type EmailSendOptions = {
|
||||||
|
appendAttribution?: boolean;
|
||||||
allowUnauthorizedCerts?: boolean;
|
allowUnauthorizedCerts?: boolean;
|
||||||
attachments?: string;
|
attachments?: string;
|
||||||
ccEmail?: string;
|
ccEmail?: string;
|
||||||
|
@ -185,6 +225,8 @@ function configureTransport(credentials: IDataObject, options: EmailSendOptions)
|
||||||
|
|
||||||
export async function execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
export async function execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
const items = this.getInputData();
|
const items = this.getInputData();
|
||||||
|
const nodeVersion = this.getNode().typeVersion;
|
||||||
|
const instanceId = await this.getInstanceId();
|
||||||
|
|
||||||
const returnData: INodeExecutionData[] = [];
|
const returnData: INodeExecutionData[] = [];
|
||||||
let item: INodeExecutionData;
|
let item: INodeExecutionData;
|
||||||
|
@ -220,6 +262,32 @@ export async function execute(this: IExecuteFunctions): Promise<INodeExecutionDa
|
||||||
mailOptions.html = this.getNodeParameter('html', itemIndex, '');
|
mailOptions.html = this.getNodeParameter('html', itemIndex, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let appendAttribution = options.appendAttribution;
|
||||||
|
if (appendAttribution === undefined) {
|
||||||
|
appendAttribution = nodeVersion >= 2.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appendAttribution) {
|
||||||
|
const attributionText = 'This email was sent automatically with ';
|
||||||
|
const link = `https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=${encodeURIComponent(
|
||||||
|
'n8n-nodes-base.emailSend',
|
||||||
|
)}${instanceId ? '_' + instanceId : ''}`;
|
||||||
|
if (emailFormat === 'html' || (emailFormat === 'both' && mailOptions.html)) {
|
||||||
|
mailOptions.html = `
|
||||||
|
${mailOptions.html}
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
---
|
||||||
|
<br>
|
||||||
|
<em>${attributionText}<a href="${link}" target="_blank">n8n</a></em>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
mailOptions.text = `${
|
||||||
|
mailOptions.text
|
||||||
|
}\n\n---\n${attributionText}n8n\n${'https://n8n.io'}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (options.attachments && item.binary) {
|
if (options.attachments && item.binary) {
|
||||||
const attachments = [];
|
const attachments = [];
|
||||||
const attachmentProperties: string[] = options.attachments
|
const attachmentProperties: string[] = options.attachments
|
||||||
|
|
|
@ -435,19 +435,43 @@ export function prepareEmailsInput(
|
||||||
export function prepareEmailBody(
|
export function prepareEmailBody(
|
||||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
|
appendAttribution = false,
|
||||||
|
instanceId?: string,
|
||||||
) {
|
) {
|
||||||
const emailType = this.getNodeParameter('emailType', itemIndex) as string;
|
const emailType = this.getNodeParameter('emailType', itemIndex) as string;
|
||||||
|
let message = (this.getNodeParameter('message', itemIndex, '') as string).trim();
|
||||||
|
|
||||||
let body = '';
|
if (appendAttribution) {
|
||||||
let htmlBody = '';
|
const attributionText = 'This email was sent automatically with ';
|
||||||
|
const link = `https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=${encodeURIComponent(
|
||||||
if (emailType === 'html') {
|
'n8n-nodes-base.gmail',
|
||||||
htmlBody = (this.getNodeParameter('message', itemIndex, '') as string).trim();
|
)}${instanceId ? '_' + instanceId : ''}`;
|
||||||
} else {
|
if (emailType === 'html') {
|
||||||
body = (this.getNodeParameter('message', itemIndex, '') as string).trim();
|
message = `
|
||||||
|
${message}
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
---
|
||||||
|
<br>
|
||||||
|
<em>${attributionText}<a href="${link}" target="_blank">n8n</a></em>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
message = `${message}\n\n---\n${attributionText}n8n\n${'https://n8n.io'}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { body, htmlBody };
|
const body = {
|
||||||
|
body: '',
|
||||||
|
htmlBody: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (emailType === 'html') {
|
||||||
|
body.htmlBody = message;
|
||||||
|
} else {
|
||||||
|
body.body = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function prepareEmailAttachments(
|
export async function prepareEmailAttachments(
|
||||||
|
|
|
@ -13,12 +13,13 @@ export class Gmail extends VersionedNodeType {
|
||||||
group: ['transform'],
|
group: ['transform'],
|
||||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
description: 'Consume the Gmail API',
|
description: 'Consume the Gmail API',
|
||||||
defaultVersion: 2,
|
defaultVersion: 2.1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
|
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
|
||||||
1: new GmailV1(baseDescription),
|
1: new GmailV1(baseDescription),
|
||||||
2: new GmailV2(baseDescription),
|
2: new GmailV2(baseDescription),
|
||||||
|
2.1: new GmailV2(baseDescription),
|
||||||
};
|
};
|
||||||
|
|
||||||
super(nodeVersions, baseDescription);
|
super(nodeVersions, baseDescription);
|
||||||
|
|
|
@ -39,7 +39,7 @@ const versionDescription: INodeTypeDescription = {
|
||||||
name: 'gmail',
|
name: 'gmail',
|
||||||
icon: 'file:gmail.svg',
|
icon: 'file:gmail.svg',
|
||||||
group: ['transform'],
|
group: ['transform'],
|
||||||
version: 2,
|
version: [2, 2.1],
|
||||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
description: 'Consume the Gmail API',
|
description: 'Consume the Gmail API',
|
||||||
defaults: {
|
defaults: {
|
||||||
|
@ -205,6 +205,8 @@ export class GmailV2 implements INodeType {
|
||||||
const returnData: INodeExecutionData[] = [];
|
const returnData: INodeExecutionData[] = [];
|
||||||
const resource = this.getNodeParameter('resource', 0);
|
const resource = this.getNodeParameter('resource', 0);
|
||||||
const operation = this.getNodeParameter('operation', 0);
|
const operation = this.getNodeParameter('operation', 0);
|
||||||
|
const nodeVersion = this.getNode().typeVersion;
|
||||||
|
const instanceId = await this.getInstanceId();
|
||||||
|
|
||||||
let responseData;
|
let responseData;
|
||||||
|
|
||||||
|
@ -323,6 +325,11 @@ export class GmailV2 implements INodeType {
|
||||||
from = `${options.senderName as string} <${emailAddress}>`;
|
from = `${options.senderName as string} <${emailAddress}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let appendAttribution = options.appendAttribution;
|
||||||
|
if (appendAttribution === undefined) {
|
||||||
|
appendAttribution = nodeVersion >= 2.1;
|
||||||
|
}
|
||||||
|
|
||||||
const email: IEmail = {
|
const email: IEmail = {
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
|
@ -330,7 +337,7 @@ export class GmailV2 implements INodeType {
|
||||||
bcc,
|
bcc,
|
||||||
replyTo,
|
replyTo,
|
||||||
subject: this.getNodeParameter('subject', i) as string,
|
subject: this.getNodeParameter('subject', i) as string,
|
||||||
...prepareEmailBody.call(this, i),
|
...prepareEmailBody.call(this, i, appendAttribution as boolean, instanceId),
|
||||||
attachments,
|
attachments,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -125,7 +125,7 @@ export const messageFields: INodeProperties[] = [
|
||||||
displayName: 'Email Type',
|
displayName: 'Email Type',
|
||||||
name: 'emailType',
|
name: 'emailType',
|
||||||
type: 'options',
|
type: 'options',
|
||||||
default: 'text',
|
default: 'html',
|
||||||
required: true,
|
required: true,
|
||||||
noDataExpression: true,
|
noDataExpression: true,
|
||||||
options: [
|
options: [
|
||||||
|
@ -143,6 +143,34 @@ export const messageFields: INodeProperties[] = [
|
||||||
resource: ['message'],
|
resource: ['message'],
|
||||||
operation: ['send', 'reply'],
|
operation: ['send', 'reply'],
|
||||||
},
|
},
|
||||||
|
hide: {
|
||||||
|
'@version': [2],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Email Type',
|
||||||
|
name: 'emailType',
|
||||||
|
type: 'options',
|
||||||
|
default: 'html',
|
||||||
|
required: true,
|
||||||
|
noDataExpression: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Text',
|
||||||
|
value: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'HTML',
|
||||||
|
value: 'html',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['message'],
|
||||||
|
operation: ['send', 'reply'],
|
||||||
|
'@version': [2],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -171,6 +199,15 @@ export const messageFields: INodeProperties[] = [
|
||||||
},
|
},
|
||||||
default: {},
|
default: {},
|
||||||
options: [
|
options: [
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
|
displayName: 'Append n8n Attribution',
|
||||||
|
name: 'appendAttribution',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description:
|
||||||
|
'Whether to include the phrase “This email was sent automatically with n8n” to the end of the email',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Attachments',
|
displayName: 'Attachments',
|
||||||
name: 'attachmentsUi',
|
name: 'attachmentsUi',
|
||||||
|
|
|
@ -120,6 +120,14 @@ export const channelMessageFields: INodeProperties[] = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Include Link to Workflow',
|
||||||
|
name: 'includeLinkToWorkflow',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description:
|
||||||
|
'Whether to append a link to this workflow at the end of the message. This is helpful if you have many workflows sending messages.',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Make Reply',
|
displayName: 'Make Reply',
|
||||||
name: 'makeReply',
|
name: 'makeReply',
|
||||||
|
|
|
@ -95,6 +95,30 @@ export const chatMessageFields: INodeProperties[] = [
|
||||||
default: '',
|
default: '',
|
||||||
description: 'The content of the item',
|
description: 'The content of the item',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
operation: ['create'],
|
||||||
|
resource: ['chatMessage'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
description: 'Other options to set',
|
||||||
|
placeholder: 'Add options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Include Link to Workflow',
|
||||||
|
name: 'includeLinkToWorkflow',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description:
|
||||||
|
'Whether to append a link to this workflow at the end of the message. This is helpful if you have many workflows sending messages.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* chatMessage:get */
|
/* chatMessage:get */
|
||||||
|
|
|
@ -89,3 +89,27 @@ export async function microsoftApiRequestAllItemsSkip(
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function prepareMessage(
|
||||||
|
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||||
|
message: string,
|
||||||
|
messageType: string,
|
||||||
|
includeLinkToWorkflow: boolean,
|
||||||
|
instanceId?: string,
|
||||||
|
) {
|
||||||
|
if (includeLinkToWorkflow) {
|
||||||
|
const { id } = this.getWorkflow();
|
||||||
|
const link = `${this.getInstanceBaseUrl()}workflow/${id}?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=${encodeURIComponent(
|
||||||
|
'n8n-nodes-base.microsoftTeams',
|
||||||
|
)}${instanceId ? '_' + instanceId : ''}`;
|
||||||
|
messageType = 'html';
|
||||||
|
message = `${message}<br><br><em> Powered by <a href="${link}">this n8n workflow</a> </em>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
body: {
|
||||||
|
contentType: messageType,
|
||||||
|
content: message,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,11 @@ import type {
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import { microsoftApiRequest, microsoftApiRequestAllItems } from './GenericFunctions';
|
import {
|
||||||
|
microsoftApiRequest,
|
||||||
|
microsoftApiRequestAllItems,
|
||||||
|
prepareMessage,
|
||||||
|
} from './GenericFunctions';
|
||||||
|
|
||||||
import { channelFields, channelOperations } from './ChannelDescription';
|
import { channelFields, channelOperations } from './ChannelDescription';
|
||||||
|
|
||||||
|
@ -24,7 +28,7 @@ export class MicrosoftTeams implements INodeType {
|
||||||
name: 'microsoftTeams',
|
name: 'microsoftTeams',
|
||||||
icon: 'file:teams.svg',
|
icon: 'file:teams.svg',
|
||||||
group: ['input'],
|
group: ['input'],
|
||||||
version: 1,
|
version: [1, 1.1],
|
||||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
description: 'Consume Microsoft Teams API',
|
description: 'Consume Microsoft Teams API',
|
||||||
defaults: {
|
defaults: {
|
||||||
|
@ -266,6 +270,9 @@ export class MicrosoftTeams implements INodeType {
|
||||||
let responseData;
|
let responseData;
|
||||||
const resource = this.getNodeParameter('resource', 0);
|
const resource = this.getNodeParameter('resource', 0);
|
||||||
const operation = this.getNodeParameter('operation', 0);
|
const operation = this.getNodeParameter('operation', 0);
|
||||||
|
const nodeVersion = this.getNode().typeVersion;
|
||||||
|
const instanceId = await this.getInstanceId();
|
||||||
|
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
try {
|
try {
|
||||||
if (resource === 'channel') {
|
if (resource === 'channel') {
|
||||||
|
@ -365,12 +372,18 @@ export class MicrosoftTeams implements INodeType {
|
||||||
const message = this.getNodeParameter('message', i) as string;
|
const message = this.getNodeParameter('message', i) as string;
|
||||||
const options = this.getNodeParameter('options', i);
|
const options = this.getNodeParameter('options', i);
|
||||||
|
|
||||||
const body: IDataObject = {
|
let includeLinkToWorkflow = options.includeLinkToWorkflow;
|
||||||
body: {
|
if (includeLinkToWorkflow === undefined) {
|
||||||
contentType: messageType,
|
includeLinkToWorkflow = nodeVersion >= 1.1;
|
||||||
content: message,
|
}
|
||||||
},
|
|
||||||
};
|
const body: IDataObject = prepareMessage.call(
|
||||||
|
this,
|
||||||
|
message,
|
||||||
|
messageType,
|
||||||
|
includeLinkToWorkflow as boolean,
|
||||||
|
instanceId,
|
||||||
|
);
|
||||||
|
|
||||||
if (options.makeReply) {
|
if (options.makeReply) {
|
||||||
const replyToId = options.makeReply as string;
|
const replyToId = options.makeReply as string;
|
||||||
|
@ -420,12 +433,19 @@ export class MicrosoftTeams implements INodeType {
|
||||||
const chatId = this.getNodeParameter('chatId', i) as string;
|
const chatId = this.getNodeParameter('chatId', i) as string;
|
||||||
const messageType = this.getNodeParameter('messageType', i) as string;
|
const messageType = this.getNodeParameter('messageType', i) as string;
|
||||||
const message = this.getNodeParameter('message', i) as string;
|
const message = this.getNodeParameter('message', i) as string;
|
||||||
const body: IDataObject = {
|
const options = this.getNodeParameter('options', i, {});
|
||||||
body: {
|
|
||||||
contentType: messageType,
|
const includeLinkToWorkflow =
|
||||||
content: message,
|
options.includeLinkToWorkflow !== false && nodeVersion >= 1.1;
|
||||||
},
|
|
||||||
};
|
const body: IDataObject = prepareMessage.call(
|
||||||
|
this,
|
||||||
|
message,
|
||||||
|
messageType,
|
||||||
|
includeLinkToWorkflow,
|
||||||
|
instanceId,
|
||||||
|
);
|
||||||
|
|
||||||
responseData = await microsoftApiRequest.call(
|
responseData = await microsoftApiRequest.call(
|
||||||
this,
|
this,
|
||||||
'POST',
|
'POST',
|
||||||
|
|
|
@ -129,9 +129,12 @@ export async function slackApiRequestAllItems(
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageContent(this: IExecuteFunctions | ILoadOptionsFunctions, i: number) {
|
export function getMessageContent(
|
||||||
const nodeVersion = this.getNode().typeVersion;
|
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||||
|
i: number,
|
||||||
|
nodeVersion: number,
|
||||||
|
instanceId?: string,
|
||||||
|
) {
|
||||||
const includeLinkToWorkflow = this.getNodeParameter(
|
const includeLinkToWorkflow = this.getNodeParameter(
|
||||||
'otherOptions.includeLinkToWorkflow',
|
'otherOptions.includeLinkToWorkflow',
|
||||||
i,
|
i,
|
||||||
|
@ -139,7 +142,9 @@ export function getMessageContent(this: IExecuteFunctions | ILoadOptionsFunction
|
||||||
) as IDataObject;
|
) as IDataObject;
|
||||||
|
|
||||||
const { id } = this.getWorkflow();
|
const { id } = this.getWorkflow();
|
||||||
const automatedMessage = `_Automated with this <${this.getInstanceBaseUrl()}workflow/${id}?utm_source=n8n&utm_medium=slackNode|n8n workflow>_`;
|
const automatedMessage = `_Automated with this <${this.getInstanceBaseUrl()}workflow/${id}?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=${encodeURIComponent(
|
||||||
|
'n8n-nodes-base.slack',
|
||||||
|
)}${instanceId ? '_' + instanceId : ''}|n8n workflow>_`;
|
||||||
const messageType = this.getNodeParameter('messageType', i) as string;
|
const messageType = this.getNodeParameter('messageType', i) as string;
|
||||||
|
|
||||||
let content: IDataObject = {};
|
let content: IDataObject = {};
|
||||||
|
|
|
@ -558,7 +558,7 @@ export const messageFields: INodeProperties[] = [
|
||||||
placeholder: 'Add options',
|
placeholder: 'Add options',
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
displayName: 'Include Link To Workflow',
|
displayName: 'Include Link to Workflow',
|
||||||
name: 'includeLinkToWorkflow',
|
name: 'includeLinkToWorkflow',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
|
|
|
@ -322,6 +322,9 @@ export class SlackV2 implements INodeType {
|
||||||
const resource = this.getNodeParameter('resource', 0);
|
const resource = this.getNodeParameter('resource', 0);
|
||||||
const operation = this.getNodeParameter('operation', 0);
|
const operation = this.getNodeParameter('operation', 0);
|
||||||
|
|
||||||
|
const nodeVersion = this.getNode().typeVersion;
|
||||||
|
const instanceId = await this.getInstanceId();
|
||||||
|
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
try {
|
try {
|
||||||
responseData = {
|
responseData = {
|
||||||
|
@ -768,7 +771,7 @@ export class SlackV2 implements INodeType {
|
||||||
target = target.slice(0, 1) === '@' ? target : `@${target}`;
|
target = target.slice(0, 1) === '@' ? target : `@${target}`;
|
||||||
}
|
}
|
||||||
const { sendAsUser } = this.getNodeParameter('otherOptions', i) as IDataObject;
|
const { sendAsUser } = this.getNodeParameter('otherOptions', i) as IDataObject;
|
||||||
const content = getMessageContent.call(this, i);
|
const content = getMessageContent.call(this, i, nodeVersion, instanceId);
|
||||||
|
|
||||||
const body: IDataObject = {
|
const body: IDataObject = {
|
||||||
channel: target,
|
channel: target,
|
||||||
|
|
|
@ -64,12 +64,51 @@ export interface IMarkupReplyKeyboardRemove {
|
||||||
* @param {IDataObject} body The body object to add fields to
|
* @param {IDataObject} body The body object to add fields to
|
||||||
* @param {number} index The index of the item
|
* @param {number} index The index of the item
|
||||||
*/
|
*/
|
||||||
export function addAdditionalFields(this: IExecuteFunctions, body: IDataObject, index: number) {
|
export function addAdditionalFields(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
body: IDataObject,
|
||||||
|
index: number,
|
||||||
|
nodeVersion?: number,
|
||||||
|
instanceId?: string,
|
||||||
|
) {
|
||||||
|
const operation = this.getNodeParameter('operation', index);
|
||||||
|
|
||||||
// Add the additional fields
|
// Add the additional fields
|
||||||
const additionalFields = this.getNodeParameter('additionalFields', index);
|
const additionalFields = this.getNodeParameter('additionalFields', index);
|
||||||
Object.assign(body, additionalFields);
|
|
||||||
|
|
||||||
const operation = this.getNodeParameter('operation', index);
|
if (operation === 'sendMessage') {
|
||||||
|
const attributionText = 'This message was sent automatically with ';
|
||||||
|
const link = `https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=${encodeURIComponent(
|
||||||
|
'n8n-nodes-base.telegram',
|
||||||
|
)}${instanceId ? '_' + instanceId : ''}`;
|
||||||
|
|
||||||
|
if (nodeVersion && nodeVersion >= 1.1 && additionalFields.appendAttribution === undefined) {
|
||||||
|
additionalFields.appendAttribution = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!additionalFields.parse_mode) {
|
||||||
|
additionalFields.parse_mode = 'Markdown';
|
||||||
|
}
|
||||||
|
|
||||||
|
const regex = /(https?|ftp|file):\/\/\S+|www\.\S+|\S+\.\S+/;
|
||||||
|
const containsUrl = regex.test(body.text as string);
|
||||||
|
|
||||||
|
if (!containsUrl) {
|
||||||
|
body.disable_web_page_preview = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.appendAttribution) {
|
||||||
|
if (additionalFields.parse_mode === 'Markdown') {
|
||||||
|
body.text = `${body.text}\n\n_${attributionText}_[n8n](${link})`;
|
||||||
|
} else if (additionalFields.parse_mode === 'HTML') {
|
||||||
|
body.text = `${body.text}\n\n<em>${attributionText}</em><a href="${link}" target="_blank">n8n</a>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete additionalFields.appendAttribution;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(body, additionalFields);
|
||||||
|
|
||||||
// Add the reply markup
|
// Add the reply markup
|
||||||
let replyMarkupOption = '';
|
let replyMarkupOption = '';
|
||||||
|
|
|
@ -17,7 +17,7 @@ export class Telegram implements INodeType {
|
||||||
name: 'telegram',
|
name: 'telegram',
|
||||||
icon: 'file:telegram.svg',
|
icon: 'file:telegram.svg',
|
||||||
group: ['output'],
|
group: ['output'],
|
||||||
version: 1,
|
version: [1, 1.1],
|
||||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
description: 'Sends data to Telegram',
|
description: 'Sends data to Telegram',
|
||||||
defaults: {
|
defaults: {
|
||||||
|
@ -1461,6 +1461,20 @@ export class Telegram implements INodeType {
|
||||||
},
|
},
|
||||||
default: {},
|
default: {},
|
||||||
options: [
|
options: [
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
|
displayName: 'Append n8n Attribution',
|
||||||
|
name: 'appendAttribution',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description:
|
||||||
|
'Whether to include the phrase “This message was sent automatically with n8n” to the end of the message',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
'/operation': ['sendMessage'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Caption',
|
displayName: 'Caption',
|
||||||
name: 'caption',
|
name: 'caption',
|
||||||
|
@ -1693,6 +1707,9 @@ export class Telegram implements INodeType {
|
||||||
const resource = this.getNodeParameter('resource', 0);
|
const resource = this.getNodeParameter('resource', 0);
|
||||||
const binaryData = this.getNodeParameter('binaryData', 0, false);
|
const binaryData = this.getNodeParameter('binaryData', 0, false);
|
||||||
|
|
||||||
|
const nodeVersion = this.getNode().typeVersion;
|
||||||
|
const instanceId = await this.getInstanceId();
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
try {
|
try {
|
||||||
// Reset all values
|
// Reset all values
|
||||||
|
@ -1917,7 +1934,7 @@ export class Telegram implements INodeType {
|
||||||
body.text = this.getNodeParameter('text', i) as string;
|
body.text = this.getNodeParameter('text', i) as string;
|
||||||
|
|
||||||
// Add additional fields and replyMarkup
|
// Add additional fields and replyMarkup
|
||||||
addAdditionalFields.call(this, body, i);
|
addAdditionalFields.call(this, body, i, nodeVersion, instanceId);
|
||||||
} else if (operation === 'sendMediaGroup') {
|
} else if (operation === 'sendMediaGroup') {
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// message:sendMediaGroup
|
// message:sendMediaGroup
|
||||||
|
|
|
@ -740,6 +740,7 @@ export interface FunctionsBase {
|
||||||
getTimezone(): string;
|
getTimezone(): string;
|
||||||
getRestApiUrl(): string;
|
getRestApiUrl(): string;
|
||||||
getInstanceBaseUrl(): string;
|
getInstanceBaseUrl(): string;
|
||||||
|
getInstanceId(): Promise<string>;
|
||||||
|
|
||||||
getMode?: () => WorkflowExecuteMode;
|
getMode?: () => WorkflowExecuteMode;
|
||||||
getActivationMode?: () => WorkflowActivateMode;
|
getActivationMode?: () => WorkflowActivateMode;
|
||||||
|
|
Loading…
Reference in a new issue