diff --git a/packages/nodes-base/nodes/LinkedIn/GenericFunctions.ts b/packages/nodes-base/nodes/LinkedIn/GenericFunctions.ts index 7dd7ea2f08adc..1c1caf9047b9e 100644 --- a/packages/nodes-base/nodes/LinkedIn/GenericFunctions.ts +++ b/packages/nodes-base/nodes/LinkedIn/GenericFunctions.ts @@ -8,6 +8,13 @@ import type { JsonObject, } from 'n8n-workflow'; import { NodeApiError } from 'n8n-workflow'; +function resolveHeaderData(fullResponse: any) { + if (fullResponse.statusCode === 201) { + return { urn: fullResponse.headers['x-restli-id'] }; + } else { + return fullResponse.body; + } +} export async function linkedInApiRequest( this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, @@ -18,17 +25,20 @@ export async function linkedInApiRequest( binary?: boolean, _headers?: object, ): Promise { - const options: OptionsWithUrl = { + let options: OptionsWithUrl = { headers: { Accept: 'application/json', 'X-Restli-Protocol-Version': '2.0.0', + 'LinkedIn-Version': '202301', }, method, body, - url: binary ? endpoint : `https://api.linkedin.com/v2${endpoint}`, + url: binary ? endpoint : `https://api.linkedin.com/rest${endpoint}`, json: true, }; - + options = Object.assign({}, options, { + resolveWithFullResponse: true, + }); // If uploading binary data if (binary) { delete options.json; @@ -40,9 +50,11 @@ export async function linkedInApiRequest( } try { - return await this.helpers.requestOAuth2.call(this, 'linkedInOAuth2Api', options, { - tokenType: 'Bearer', - }); + return resolveHeaderData( + await this.helpers.requestOAuth2.call(this, 'linkedInOAuth2Api', options, { + tokenType: 'Bearer', + }), + ); } catch (error) { throw new NodeApiError(this.getNode(), error as JsonObject); } diff --git a/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts b/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts index 72ca14795bf55..434fa2cc8c8f9 100644 --- a/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts +++ b/packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts @@ -102,6 +102,17 @@ export class LinkedIn implements INodeType { let title = ''; let originalUrl = ''; + body = { + author: authorUrn, + lifecycleState: 'PUBLISHED', + distribution: { + feedDistribution: 'MAIN_FEED', + targetEnties: [], + thirdPartyDistributionChannels: [], + }, + visibility, + }; + if (shareMediaCategory === 'IMAGE') { if (additionalFields.description) { description = additionalFields.description as string; @@ -111,66 +122,36 @@ export class LinkedIn implements INodeType { } // Send a REQUEST to prepare a register of a media image file const registerRequest = { - registerUploadRequest: { - recipes: ['urn:li:digitalmediaRecipe:feedshare-image'], + initializeUploadRequest: { owner: authorUrn, - serviceRelationships: [ - { - relationshipType: 'OWNER', - identifier: 'urn:li:userGeneratedContent', - }, - ], }, }; const registerObject = await linkedInApiRequest.call( this, 'POST', - '/assets?action=registerUpload', + '/images?action=initializeUpload', registerRequest, ); - // Response provides a specific upload URL that is used to upload the binary image file - const uploadUrl = registerObject.value.uploadMechanism[ - 'com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest' - ].uploadUrl as string; - const asset = registerObject.value.asset as string; - const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i); this.helpers.assertBinaryData(i, binaryPropertyName); - // Buffer binary data const buffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName); - // Upload image - await linkedInApiRequest.call(this, 'POST', uploadUrl, buffer, true); - - body = { - author: authorUrn, - lifecycleState: 'PUBLISHED', - specificContent: { - 'com.linkedin.ugc.ShareContent': { - shareCommentary: { - text, - }, - shareMediaCategory: 'IMAGE', - media: [ - { - status: 'READY', - description: { - text: description, - }, - media: asset, - title: { - text: title, - }, - }, - ], + const { uploadUrl, image } = registerObject.value; + await linkedInApiRequest.call(this, 'POST', uploadUrl as string, buffer, true); + + const imageBody = { + content: { + media: { + title, + id: image, + description, }, }, - visibility: { - 'com.linkedin.ugc.MemberNetworkVisibility': visibility, - }, + commentary: text, }; + Object.assign(body, imageBody); } else if (shareMediaCategory === 'ARTICLE') { if (additionalFields.description) { description = additionalFields.description as string; @@ -182,60 +163,28 @@ export class LinkedIn implements INodeType { originalUrl = additionalFields.originalUrl as string; } - body = { - author: `${authorUrn}`, - lifecycleState: 'PUBLISHED', - specificContent: { - 'com.linkedin.ugc.ShareContent': { - shareCommentary: { - text, - }, - shareMediaCategory, - media: [ - { - status: 'READY', - description: { - text: description, - }, - originalUrl, - title: { - text: title, - }, - }, - ], + const articleBody = { + content: { + article: { + title, + description, + source: originalUrl, }, }, - visibility: { - 'com.linkedin.ugc.MemberNetworkVisibility': visibility, - }, + commentary: text, }; - + Object.assign(body, articleBody); if (description === '') { - delete body.specificContent['com.linkedin.ugc.ShareContent'].media[0].description; + delete body.description; } if (title === '') { - delete body.specificContent['com.linkedin.ugc.ShareContent'].media[0].title; + delete body.title; } } else { - body = { - author: authorUrn, - lifecycleState: 'PUBLISHED', - specificContent: { - 'com.linkedin.ugc.ShareContent': { - shareCommentary: { - text, - }, - shareMediaCategory, - }, - }, - visibility: { - 'com.linkedin.ugc.MemberNetworkVisibility': visibility, - }, - }; + Object.assign(body, { commentary: text }); } - - const endpoint = '/ugcPosts'; + const endpoint = '/posts'; responseData = await linkedInApiRequest.call(this, 'POST', endpoint, body); } }