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:
Michael Kret 2023-10-03 11:18:59 +03:00 committed by GitHub
parent f0a66873b9
commit 8f9fe6269b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 345 additions and 57 deletions

View file

@ -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],

View file

@ -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';

View file

@ -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;
} }
} }
} }

View file

@ -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);

View file

@ -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',

View file

@ -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

View file

@ -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(

View file

@ -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);

View file

@ -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,
}; };

View file

@ -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',

View file

@ -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',

View file

@ -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 */

View file

@ -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,
},
};
}

View file

@ -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',

View file

@ -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 = {};

View file

@ -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,

View file

@ -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,

View file

@ -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 = '';

View file

@ -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

View file

@ -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;