Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 5 commits into from
Mar 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions packages/nodes-base/nodes/LinkedIn/GenericFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand All @@ -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);
}
Expand Down
123 changes: 36 additions & 87 deletions packages/nodes-base/nodes/LinkedIn/LinkedIn.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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);
}
}
Expand Down