index.ts•6.73 kB
import { ApFile, Property } from '@activepieces/pieces-framework';
import {
HttpMethod,
httpClient,
AuthenticationType,
} from '@activepieces/pieces-common';
import FormData from 'form-data';
export const santizeText = (text: string) => {
// LinkedIn Posts API has a list of characters that need to be escaped since it's type is "LittleText"
// https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api?view=li-lms-2023-11&tabs=http
// https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/little-text-format?view=li-lms-2023-11
// eslint-disable-next-line no-useless-escape
return text.replace(/[\(*\)\[\]\{\}<>@|~_]/gm, (x: string) => '\\' + x);
};
export const linkedinCommon = {
baseUrl: 'https://api.linkedin.com',
linkedinHeaders: {
'X-Restli-Protocol-Version': '2.0.0',
'LinkedIn-Version': '202411',
},
text: Property.LongText({
displayName: 'Text',
required: true,
}),
imageUrl: Property.File({
displayName: 'Image',
required: false,
}),
link: Property.ShortText({
displayName: 'Content - URL',
required: false,
}),
linkTitle: Property.ShortText({
displayName: 'Content - Title',
required: false,
}),
linkDescription: Property.ShortText({
displayName: 'Content - Description',
required: false,
}),
visibility: Property.Dropdown({
displayName: 'Visibility',
refreshers: [],
required: true,
options: async () => {
return {
options: [
{
label: 'Public',
value: 'PUBLIC',
},
{
label: 'Connections Only',
value: 'CONNECTIONS',
},
],
};
},
}),
company: Property.Dropdown({
displayName: 'Company Page',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
placeholder: 'Connect your account',
options: [],
};
}
const authProp = auth as { access_token: string };
const companies: any = await linkedinCommon.getCompanies(
authProp.access_token
);
const options = [];
for (const company in companies) {
options.push({
label: companies[company].localizedName,
value: companies[company].id,
});
}
return {
options: options,
};
},
}),
getCompanies: async (accessToken: string) => {
const companies = (
await httpClient.sendRequest({
url: `${linkedinCommon.baseUrl}/v2/organizationalEntityAcls`,
method: HttpMethod.GET,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: accessToken,
},
queryParams: {
q: 'roleAssignee',
},
})
).body;
const companyIds = companies.elements.map(
(company: { organizationalTarget: string }) => {
return company.organizationalTarget.substr(
company.organizationalTarget.lastIndexOf(':') + 1
);
}
);
const response = await fetch(`${linkedinCommon.baseUrl}/rest/organizations?ids=List(${companyIds.join(',')})`, {
method: 'GET',
headers: {
...linkedinCommon.linkedinHeaders,
'Authorization': `Bearer ${accessToken}`
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const companySearch = await response.json();
return companySearch.results;
},
generatePostRequestBody: (data: {
urn: string;
text: any;
link?: string | undefined;
linkTitle?: string | undefined;
linkDescription?: string | undefined;
visibility: string;
image?: Image | undefined;
}) => {
const requestObject: Post = {
author: `urn:li:${data.urn}`,
lifecycleState: 'PUBLISHED',
commentary: data.text,
distribution: {
feedDistribution: 'MAIN_FEED',
},
visibility: data.visibility,
isReshareDisabledByAuthor: false,
};
if (data.link) {
requestObject.content = {
article: {
source: data.link,
title: data.linkTitle,
description: data.linkDescription,
thumbnail: data.image?.value.image,
},
};
} else if (data.image) {
requestObject.content = {
media: {
id: data.image.value.image,
},
};
}
return requestObject;
},
uploadImage: async (
accessToken: string,
urn: string,
image: ApFile
): Promise<Image> => {
const uploadData = (
await httpClient.sendRequest({
method: HttpMethod.POST,
url: `${linkedinCommon.baseUrl}/v2/images`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: accessToken,
},
queryParams: {
action: 'initializeUpload',
},
body: {
initializeUploadRequest: {
owner: `urn:li:${urn}`,
},
},
})
).body as Image;
const uploadFormData = new FormData();
const { filename, base64 } = image;
uploadFormData.append('file', Buffer.from(base64, 'base64'), filename);
await httpClient.sendRequest({
url: uploadData.value.uploadUrl,
method: HttpMethod.POST,
body: uploadFormData,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: accessToken,
},
headers: {
'Content-Type': 'multipart/form-data',
},
});
return uploadData;
},
};
export interface UgcPost {
author: string;
lifecycleState: string;
specificContent: {
'com.linkedin.ugc.ShareContent': {
shareCommentary: {
text?: string;
};
shareMediaCategory: string;
media?: [
{
status: string;
description?: {
text: string;
};
originalUrl?: string;
thumbnail?: string;
title?: {
text: string;
};
}
];
};
};
visibility: {
'com.linkedin.ugc.MemberNetworkVisibility': string;
};
}
export interface Post {
author: string;
commentary: string;
lifecycleState: string;
visibility: string;
distribution: {
feedDistribution: string;
};
content?: {
article?: {
source: string;
thumbnail?: string | undefined;
title?: string | undefined;
description?: string | undefined;
};
media?: {
id: string;
};
};
isReshareDisabledByAuthor: boolean;
}
export interface Image {
value: {
uploadUrlExpiresAt: number;
uploadUrl: string;
image: string;
};
}