fix(LinkedIn Node): Update the version of the API (#5720)

* 🐛 Change request to follow new API version

* Extract urn from response header

* Change body params for image and media request

* Fix body for Image and Article posts

* remove console log

---------

Co-authored-by: Marcus <marcus@n8n.io>
This commit is contained in:
agobrech 2023-03-31 15:04:43 +02:00 committed by GitHub
parent 97b35daf0a
commit 18d2e7cd57
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 53 additions and 92 deletions

View file

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

View file

@ -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);
const { uploadUrl, image } = registerObject.value;
await linkedInApiRequest.call(this, 'POST', uploadUrl as string, 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 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);
}
}