diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index e85d4330f08..f85b366e3f0 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -143,7 +143,7 @@ import { setWorkflowExecutionMetadata, } from './WorkflowExecutionMetadata'; import { getSecretsProxy } from './Secrets'; -import { getUserN8nFolderPath } from './UserSettings'; +import { getUserN8nFolderPath, getInstanceId } from './UserSettings'; import Container from 'typedi'; import type { BinaryData } from './BinaryData/types'; @@ -2506,6 +2506,7 @@ const getCommonWorkflowFunctions = ( getRestApiUrl: () => additionalData.restApiUrl, getInstanceBaseUrl: () => additionalData.instanceBaseUrl, + getInstanceId: async () => getInstanceId(), getTimezone: () => getTimezone(workflow, additionalData), prepareOutputData: async (outputData) => [outputData], diff --git a/packages/editor-ui/src/constants.ts b/packages/editor-ui/src/constants.ts index 6d4e392a7c0..3d479608bb3 100644 --- a/packages/editor-ui/src/constants.ts +++ b/packages/editor-ui/src/constants.ts @@ -103,6 +103,7 @@ export const FILTER_NODE_TYPE = 'n8n-nodes-base.filter'; export const FUNCTION_NODE_TYPE = 'n8n-nodes-base.function'; export const GITHUB_TRIGGER_NODE_TYPE = 'n8n-nodes-base.githubTrigger'; 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 ERROR_TRIGGER_NODE_TYPE = 'n8n-nodes-base.errorTrigger'; 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 START_NODE_TYPE = 'n8n-nodes-base.start'; 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 QUICKBOOKS_NODE_TYPE = 'n8n-nodes-base.quickbooks'; 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_MIN_PROMPT_LENGTH = 15; export const ASK_AI_LOADING_DURATION_MS = 12000; + +export const APPEND_ATTRIBUTION_DEFAULT_PATH = 'parameters.options.appendAttribution'; diff --git a/packages/editor-ui/src/plugins/telemetry/index.ts b/packages/editor-ui/src/plugins/telemetry/index.ts index 78a4e0651dc..5663f26f185 100644 --- a/packages/editor-ui/src/plugins/telemetry/index.ts +++ b/packages/editor-ui/src/plugins/telemetry/index.ts @@ -7,7 +7,12 @@ import type { IUserNodesPanelSession } from './telemetry.types'; import { useSettingsStore } from '@/stores/settings.store'; import { useRootStore } from '@/stores/n8nRoot.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 { useNDVStore } from '@/stores'; @@ -230,22 +235,21 @@ export class Telemetry { // so we are using this method as centralized way to track node parameters changes trackNodeParametersValuesChange(nodeType: string, change: IUpdateInformation) { if (this.rudderStack) { - switch (nodeType) { - case SLACK_NODE_TYPE: - if (change.name === 'parameters.otherOptions.includeLinkToWorkflow') { - this.track( - 'User toggled n8n reference option', - { - node: nodeType, - toValue: change.value, - }, - { withPostHog: true }, - ); - } - break; - - default: - break; + const changeNameMap: { [key: string]: string } = { + [SLACK_NODE_TYPE]: 'parameters.otherOptions.includeLinkToWorkflow', + [MICROSOFT_TEAMS_NODE_TYPE]: 'parameters.options.includeLinkToWorkflow', + [TELEGRAM_NODE_TYPE]: 'parameters.additionalFields.appendAttribution', + }; + const changeName = changeNameMap[nodeType] || APPEND_ATTRIBUTION_DEFAULT_PATH; + if (change.name === changeName) { + this.track( + 'User toggled n8n reference option', + { + node: nodeType, + toValue: change.value, + }, + { withPostHog: true }, + ); } } } diff --git a/packages/nodes-base/nodes/EmailSend/EmailSend.node.ts b/packages/nodes-base/nodes/EmailSend/EmailSend.node.ts index e005818fcab..9f68f84eb00 100644 --- a/packages/nodes-base/nodes/EmailSend/EmailSend.node.ts +++ b/packages/nodes-base/nodes/EmailSend/EmailSend.node.ts @@ -11,13 +11,14 @@ export class EmailSend extends VersionedNodeType { name: 'emailSend', icon: 'fa:envelope', group: ['output'], - defaultVersion: 2, + defaultVersion: 2.1, description: 'Sends an email using SMTP protocol', }; const nodeVersions: IVersionedNodeType['nodeVersions'] = { 1: new EmailSendV1(baseDescription), 2: new EmailSendV2(baseDescription), + 2.1: new EmailSendV2(baseDescription), }; super(nodeVersions, baseDescription); diff --git a/packages/nodes-base/nodes/EmailSend/v2/EmailSendV2.node.ts b/packages/nodes-base/nodes/EmailSend/v2/EmailSendV2.node.ts index a7dfc3b638f..afb41c86234 100644 --- a/packages/nodes-base/nodes/EmailSend/v2/EmailSendV2.node.ts +++ b/packages/nodes-base/nodes/EmailSend/v2/EmailSendV2.node.ts @@ -15,7 +15,7 @@ const versionDescription: INodeTypeDescription = { name: 'emailSend', icon: 'fa:envelope', group: ['output'], - version: 2, + version: [2, 2.1], description: 'Sends an email using SMTP protocol', defaults: { name: 'Send Email', diff --git a/packages/nodes-base/nodes/EmailSend/v2/send.operation.ts b/packages/nodes-base/nodes/EmailSend/v2/send.operation.ts index c8acb96e6fb..c77730d9470 100644 --- a/packages/nodes-base/nodes/EmailSend/v2/send.operation.ts +++ b/packages/nodes-base/nodes/EmailSend/v2/send.operation.ts @@ -43,6 +43,31 @@ const properties: INodeProperties[] = [ placeholder: 'My subject line', 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', name: 'emailFormat', @@ -62,6 +87,11 @@ const properties: INodeProperties[] = [ }, ], default: 'text', + displayOptions: { + show: { + '@version': [2], + }, + }, }, { displayName: 'Text', @@ -100,6 +130,15 @@ const properties: INodeProperties[] = [ placeholder: 'Add Option', default: {}, 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', name: 'attachments', @@ -153,6 +192,7 @@ const displayOptions = { export const description = updateDisplayOptions(displayOptions, properties); type EmailSendOptions = { + appendAttribution?: boolean; allowUnauthorizedCerts?: boolean; attachments?: string; ccEmail?: string; @@ -185,6 +225,8 @@ function configureTransport(credentials: IDataObject, options: EmailSendOptions) export async function execute(this: IExecuteFunctions): Promise { const items = this.getInputData(); + const nodeVersion = this.getNode().typeVersion; + const instanceId = await this.getInstanceId(); const returnData: INodeExecutionData[] = []; let item: INodeExecutionData; @@ -220,6 +262,32 @@ export async function execute(this: IExecuteFunctions): Promise= 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} +
+
+ --- +
+ ${attributionText}n8n + `; + } else { + mailOptions.text = `${ + mailOptions.text + }\n\n---\n${attributionText}n8n\n${'https://n8n.io'}`; + } + } + if (options.attachments && item.binary) { const attachments = []; const attachmentProperties: string[] = options.attachments diff --git a/packages/nodes-base/nodes/Google/Gmail/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Gmail/GenericFunctions.ts index 8aee320407a..b9bfe8f7f93 100644 --- a/packages/nodes-base/nodes/Google/Gmail/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Gmail/GenericFunctions.ts @@ -435,19 +435,43 @@ export function prepareEmailsInput( export function prepareEmailBody( this: IExecuteFunctions | ILoadOptionsFunctions, itemIndex: number, + appendAttribution = false, + instanceId?: string, ) { const emailType = this.getNodeParameter('emailType', itemIndex) as string; + let message = (this.getNodeParameter('message', itemIndex, '') as string).trim(); - let body = ''; - let htmlBody = ''; - - if (emailType === 'html') { - htmlBody = (this.getNodeParameter('message', itemIndex, '') as string).trim(); - } else { - body = (this.getNodeParameter('message', itemIndex, '') as string).trim(); + 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.gmail', + )}${instanceId ? '_' + instanceId : ''}`; + if (emailType === 'html') { + message = ` + ${message} +
+
+ --- +
+ ${attributionText}n8n + `; + } 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( diff --git a/packages/nodes-base/nodes/Google/Gmail/Gmail.node.ts b/packages/nodes-base/nodes/Google/Gmail/Gmail.node.ts index 56ed095bf20..fb6eda49851 100644 --- a/packages/nodes-base/nodes/Google/Gmail/Gmail.node.ts +++ b/packages/nodes-base/nodes/Google/Gmail/Gmail.node.ts @@ -13,12 +13,13 @@ export class Gmail extends VersionedNodeType { group: ['transform'], subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Consume the Gmail API', - defaultVersion: 2, + defaultVersion: 2.1, }; const nodeVersions: IVersionedNodeType['nodeVersions'] = { 1: new GmailV1(baseDescription), 2: new GmailV2(baseDescription), + 2.1: new GmailV2(baseDescription), }; super(nodeVersions, baseDescription); diff --git a/packages/nodes-base/nodes/Google/Gmail/v2/GmailV2.node.ts b/packages/nodes-base/nodes/Google/Gmail/v2/GmailV2.node.ts index a228e6f4966..6cb9f4f7c07 100644 --- a/packages/nodes-base/nodes/Google/Gmail/v2/GmailV2.node.ts +++ b/packages/nodes-base/nodes/Google/Gmail/v2/GmailV2.node.ts @@ -39,7 +39,7 @@ const versionDescription: INodeTypeDescription = { name: 'gmail', icon: 'file:gmail.svg', group: ['transform'], - version: 2, + version: [2, 2.1], subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Consume the Gmail API', defaults: { @@ -205,6 +205,8 @@ export class GmailV2 implements INodeType { const returnData: INodeExecutionData[] = []; const resource = this.getNodeParameter('resource', 0); const operation = this.getNodeParameter('operation', 0); + const nodeVersion = this.getNode().typeVersion; + const instanceId = await this.getInstanceId(); let responseData; @@ -323,6 +325,11 @@ export class GmailV2 implements INodeType { from = `${options.senderName as string} <${emailAddress}>`; } + let appendAttribution = options.appendAttribution; + if (appendAttribution === undefined) { + appendAttribution = nodeVersion >= 2.1; + } + const email: IEmail = { from, to, @@ -330,7 +337,7 @@ export class GmailV2 implements INodeType { bcc, replyTo, subject: this.getNodeParameter('subject', i) as string, - ...prepareEmailBody.call(this, i), + ...prepareEmailBody.call(this, i, appendAttribution as boolean, instanceId), attachments, }; diff --git a/packages/nodes-base/nodes/Google/Gmail/v2/MessageDescription.ts b/packages/nodes-base/nodes/Google/Gmail/v2/MessageDescription.ts index d42296cd2b2..0f0875fd49d 100644 --- a/packages/nodes-base/nodes/Google/Gmail/v2/MessageDescription.ts +++ b/packages/nodes-base/nodes/Google/Gmail/v2/MessageDescription.ts @@ -125,7 +125,7 @@ export const messageFields: INodeProperties[] = [ displayName: 'Email Type', name: 'emailType', type: 'options', - default: 'text', + default: 'html', required: true, noDataExpression: true, options: [ @@ -143,6 +143,34 @@ export const messageFields: INodeProperties[] = [ resource: ['message'], 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: {}, 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', name: 'attachmentsUi', diff --git a/packages/nodes-base/nodes/Microsoft/Teams/ChannelMessageDescription.ts b/packages/nodes-base/nodes/Microsoft/Teams/ChannelMessageDescription.ts index 1daf7561824..1f3b469bd66 100644 --- a/packages/nodes-base/nodes/Microsoft/Teams/ChannelMessageDescription.ts +++ b/packages/nodes-base/nodes/Microsoft/Teams/ChannelMessageDescription.ts @@ -120,6 +120,14 @@ export const channelMessageFields: INodeProperties[] = [ }, }, 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', name: 'makeReply', diff --git a/packages/nodes-base/nodes/Microsoft/Teams/ChatMessageDescription.ts b/packages/nodes-base/nodes/Microsoft/Teams/ChatMessageDescription.ts index 1a6aacd3712..ccc309e565c 100644 --- a/packages/nodes-base/nodes/Microsoft/Teams/ChatMessageDescription.ts +++ b/packages/nodes-base/nodes/Microsoft/Teams/ChatMessageDescription.ts @@ -95,6 +95,30 @@ export const chatMessageFields: INodeProperties[] = [ default: '', 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 */ diff --git a/packages/nodes-base/nodes/Microsoft/Teams/GenericFunctions.ts b/packages/nodes-base/nodes/Microsoft/Teams/GenericFunctions.ts index 3b6b95e9c69..b54a1c0f7d2 100644 --- a/packages/nodes-base/nodes/Microsoft/Teams/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Microsoft/Teams/GenericFunctions.ts @@ -89,3 +89,27 @@ export async function microsoftApiRequestAllItemsSkip( 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}

Powered by this n8n workflow `; + } + + return { + body: { + contentType: messageType, + content: message, + }, + }; +} diff --git a/packages/nodes-base/nodes/Microsoft/Teams/MicrosoftTeams.node.ts b/packages/nodes-base/nodes/Microsoft/Teams/MicrosoftTeams.node.ts index 32cd2c1acab..eb07e7348d9 100644 --- a/packages/nodes-base/nodes/Microsoft/Teams/MicrosoftTeams.node.ts +++ b/packages/nodes-base/nodes/Microsoft/Teams/MicrosoftTeams.node.ts @@ -8,7 +8,11 @@ import type { INodeTypeDescription, } from 'n8n-workflow'; -import { microsoftApiRequest, microsoftApiRequestAllItems } from './GenericFunctions'; +import { + microsoftApiRequest, + microsoftApiRequestAllItems, + prepareMessage, +} from './GenericFunctions'; import { channelFields, channelOperations } from './ChannelDescription'; @@ -24,7 +28,7 @@ export class MicrosoftTeams implements INodeType { name: 'microsoftTeams', icon: 'file:teams.svg', group: ['input'], - version: 1, + version: [1, 1.1], subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Consume Microsoft Teams API', defaults: { @@ -266,6 +270,9 @@ export class MicrosoftTeams implements INodeType { let responseData; const resource = this.getNodeParameter('resource', 0); const operation = this.getNodeParameter('operation', 0); + const nodeVersion = this.getNode().typeVersion; + const instanceId = await this.getInstanceId(); + for (let i = 0; i < length; i++) { try { if (resource === 'channel') { @@ -365,12 +372,18 @@ export class MicrosoftTeams implements INodeType { const message = this.getNodeParameter('message', i) as string; const options = this.getNodeParameter('options', i); - const body: IDataObject = { - body: { - contentType: messageType, - content: message, - }, - }; + let includeLinkToWorkflow = options.includeLinkToWorkflow; + if (includeLinkToWorkflow === undefined) { + includeLinkToWorkflow = nodeVersion >= 1.1; + } + + const body: IDataObject = prepareMessage.call( + this, + message, + messageType, + includeLinkToWorkflow as boolean, + instanceId, + ); if (options.makeReply) { const replyToId = options.makeReply as string; @@ -420,12 +433,19 @@ export class MicrosoftTeams implements INodeType { const chatId = this.getNodeParameter('chatId', i) as string; const messageType = this.getNodeParameter('messageType', i) as string; const message = this.getNodeParameter('message', i) as string; - const body: IDataObject = { - body: { - contentType: messageType, - content: message, - }, - }; + const options = this.getNodeParameter('options', i, {}); + + const includeLinkToWorkflow = + options.includeLinkToWorkflow !== false && nodeVersion >= 1.1; + + const body: IDataObject = prepareMessage.call( + this, + message, + messageType, + includeLinkToWorkflow, + instanceId, + ); + responseData = await microsoftApiRequest.call( this, 'POST', diff --git a/packages/nodes-base/nodes/Slack/V2/GenericFunctions.ts b/packages/nodes-base/nodes/Slack/V2/GenericFunctions.ts index 4d3c8bed03e..bba64ba395f 100644 --- a/packages/nodes-base/nodes/Slack/V2/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Slack/V2/GenericFunctions.ts @@ -129,9 +129,12 @@ export async function slackApiRequestAllItems( return returnData; } -export function getMessageContent(this: IExecuteFunctions | ILoadOptionsFunctions, i: number) { - const nodeVersion = this.getNode().typeVersion; - +export function getMessageContent( + this: IExecuteFunctions | ILoadOptionsFunctions, + i: number, + nodeVersion: number, + instanceId?: string, +) { const includeLinkToWorkflow = this.getNodeParameter( 'otherOptions.includeLinkToWorkflow', i, @@ -139,7 +142,9 @@ export function getMessageContent(this: IExecuteFunctions | ILoadOptionsFunction ) as IDataObject; 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; let content: IDataObject = {}; diff --git a/packages/nodes-base/nodes/Slack/V2/MessageDescription.ts b/packages/nodes-base/nodes/Slack/V2/MessageDescription.ts index a16aedece5e..527e687af26 100644 --- a/packages/nodes-base/nodes/Slack/V2/MessageDescription.ts +++ b/packages/nodes-base/nodes/Slack/V2/MessageDescription.ts @@ -558,7 +558,7 @@ export const messageFields: INodeProperties[] = [ placeholder: 'Add options', options: [ { - displayName: 'Include Link To Workflow', + displayName: 'Include Link to Workflow', name: 'includeLinkToWorkflow', type: 'boolean', default: true, diff --git a/packages/nodes-base/nodes/Slack/V2/SlackV2.node.ts b/packages/nodes-base/nodes/Slack/V2/SlackV2.node.ts index 17ece328672..be1fe2079ae 100644 --- a/packages/nodes-base/nodes/Slack/V2/SlackV2.node.ts +++ b/packages/nodes-base/nodes/Slack/V2/SlackV2.node.ts @@ -322,6 +322,9 @@ export class SlackV2 implements INodeType { const resource = this.getNodeParameter('resource', 0); const operation = this.getNodeParameter('operation', 0); + const nodeVersion = this.getNode().typeVersion; + const instanceId = await this.getInstanceId(); + for (let i = 0; i < length; i++) { try { responseData = { @@ -768,7 +771,7 @@ export class SlackV2 implements INodeType { target = target.slice(0, 1) === '@' ? target : `@${target}`; } 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 = { channel: target, diff --git a/packages/nodes-base/nodes/Telegram/GenericFunctions.ts b/packages/nodes-base/nodes/Telegram/GenericFunctions.ts index d12215f68ff..51de0a9adc2 100644 --- a/packages/nodes-base/nodes/Telegram/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Telegram/GenericFunctions.ts @@ -64,12 +64,51 @@ export interface IMarkupReplyKeyboardRemove { * @param {IDataObject} body The body object to add fields to * @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 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${attributionText}n8n`; + } + } + + delete additionalFields.appendAttribution; + } + + Object.assign(body, additionalFields); // Add the reply markup let replyMarkupOption = ''; diff --git a/packages/nodes-base/nodes/Telegram/Telegram.node.ts b/packages/nodes-base/nodes/Telegram/Telegram.node.ts index fcc4700f1c7..0e244a3b4f2 100644 --- a/packages/nodes-base/nodes/Telegram/Telegram.node.ts +++ b/packages/nodes-base/nodes/Telegram/Telegram.node.ts @@ -17,7 +17,7 @@ export class Telegram implements INodeType { name: 'telegram', icon: 'file:telegram.svg', group: ['output'], - version: 1, + version: [1, 1.1], subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Sends data to Telegram', defaults: { @@ -1461,6 +1461,20 @@ export class Telegram implements INodeType { }, default: {}, 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', name: 'caption', @@ -1693,6 +1707,9 @@ export class Telegram implements INodeType { const resource = this.getNodeParameter('resource', 0); 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++) { try { // Reset all values @@ -1917,7 +1934,7 @@ export class Telegram implements INodeType { body.text = this.getNodeParameter('text', i) as string; // Add additional fields and replyMarkup - addAdditionalFields.call(this, body, i); + addAdditionalFields.call(this, body, i, nodeVersion, instanceId); } else if (operation === 'sendMediaGroup') { // ---------------------------------- // message:sendMediaGroup diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index a61418af72d..70ef2a6d12b 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -740,6 +740,7 @@ export interface FunctionsBase { getTimezone(): string; getRestApiUrl(): string; getInstanceBaseUrl(): string; + getInstanceId(): Promise; getMode?: () => WorkflowExecuteMode; getActivationMode?: () => WorkflowActivateMode;