postiz/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts

332 lines
9.1 KiB
TypeScript

import {
AnalyticsData,
AuthTokenDetails,
PostDetails,
PostResponse,
SocialProvider,
} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
import { PinterestSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/pinterest.dto';
import axios from 'axios';
import FormData from 'form-data';
import { timer } from '@gitroom/helpers/utils/timer';
import { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';
import dayjs from 'dayjs';
export class PinterestProvider
extends SocialAbstract
implements SocialProvider
{
identifier = 'pinterest';
name = 'Pinterest';
isBetweenSteps = false;
scopes = [
'boards:read',
'boards:write',
'pins:read',
'pins:write',
'user_accounts:read',
];
editor = 'normal' as const;
async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {
const { access_token, expires_in } = await (
await this.fetch('https://api.pinterest.com/v5/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${Buffer.from(
`${process.env.PINTEREST_CLIENT_ID}:${process.env.PINTEREST_CLIENT_SECRET}`
).toString('base64')}`,
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
scope: this.scopes.join(','),
redirect_uri: `${process.env.FRONTEND_URL}/integrations/social/pinterest`,
}),
})
).json();
const { id, profile_image, username } = await (
await this.fetch('https://api.pinterest.com/v5/user_account', {
method: 'GET',
headers: {
Authorization: `Bearer ${access_token}`,
},
})
).json();
return {
id: id,
name: username,
accessToken: access_token,
refreshToken: refreshToken,
expiresIn: expires_in,
picture: profile_image,
username,
};
}
async generateAuthUrl() {
const state = makeId(6);
return {
url: `https://www.pinterest.com/oauth/?client_id=${
process.env.PINTEREST_CLIENT_ID
}&redirect_uri=${encodeURIComponent(
`${process.env.FRONTEND_URL}/integrations/social/pinterest`
)}&response_type=code&scope=${encodeURIComponent(
'boards:read,boards:write,pins:read,pins:write,user_accounts:read'
)}&state=${state}`,
codeVerifier: makeId(10),
state,
};
}
async authenticate(params: {
code: string;
codeVerifier: string;
refresh: string;
}) {
const { access_token, refresh_token, expires_in, scope } = await (
await this.fetch('https://api.pinterest.com/v5/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${Buffer.from(
`${process.env.PINTEREST_CLIENT_ID}:${process.env.PINTEREST_CLIENT_SECRET}`
).toString('base64')}`,
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: params.code,
redirect_uri: `${process.env.FRONTEND_URL}/integrations/social/pinterest`,
}),
})
).json();
this.checkScopes(this.scopes, scope);
const { id, profile_image, username } = await (
await this.fetch('https://api.pinterest.com/v5/user_account', {
method: 'GET',
headers: {
Authorization: `Bearer ${access_token}`,
},
})
).json();
return {
id: id,
name: username,
accessToken: access_token,
refreshToken: refresh_token,
expiresIn: expires_in,
picture: profile_image,
username,
};
}
async boards(accessToken: string) {
const { items } = await (
await this.fetch('https://api.pinterest.com/v5/boards', {
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
).json();
return (
items?.map((item: any) => ({
name: item.name,
id: item.id,
})) || []
);
}
async post(
id: string,
accessToken: string,
postDetails: PostDetails<PinterestSettingsDto>[]
): Promise<PostResponse[]> {
let mediaId = '';
const findMp4 = postDetails?.[0]?.media?.find(
(p) => (p.path?.indexOf('mp4') || -1) > -1
);
const picture = postDetails?.[0]?.media?.find(
(p) => (p.path?.indexOf('mp4') || -1) === -1
);
if (findMp4) {
const { upload_url, media_id, upload_parameters } = await (
await this.fetch('https://api.pinterest.com/v5/media', {
method: 'POST',
body: JSON.stringify({
media_type: 'video',
}),
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})
).json();
const { data, status } = await axios.get(
postDetails?.[0]?.media?.[0]?.path!,
{
responseType: 'stream',
}
);
const formData = Object.keys(upload_parameters)
.filter((f) => f)
.reduce((acc, key) => {
acc.append(key, upload_parameters[key]);
return acc;
}, new FormData());
formData.append('file', data);
await axios.post(upload_url, formData);
let statusCode = '';
while (statusCode !== 'succeeded') {
const mediafile = await (
await this.fetch('https://api.pinterest.com/v5/media/' + media_id, {
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`,
},
})
).json();
await timer(3000);
statusCode = mediafile.status;
}
mediaId = media_id;
}
const mapImages = postDetails?.[0]?.media?.map((m) => ({
path: m.path,
}));
try {
const { id: pId } = await (
await this.fetch('https://api.pinterest.com/v5/pins', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
...(postDetails?.[0]?.settings.link
? { link: postDetails?.[0]?.settings.link }
: {}),
...(postDetails?.[0]?.settings.title
? { title: postDetails?.[0]?.settings.title }
: {}),
description: postDetails?.[0]?.message,
...(postDetails?.[0]?.settings.dominant_color
? { dominant_color: postDetails?.[0]?.settings.dominant_color }
: {}),
board_id: postDetails?.[0]?.settings.board,
media_source: mediaId
? {
source_type: 'video_id',
media_id: mediaId,
cover_image_url: picture?.path,
}
: mapImages?.length === 1
? {
source_type: 'image_url',
url: mapImages?.[0]?.path,
}
: {
source_type: 'multiple_image_urls',
items: mapImages,
},
}),
})
).json();
return [
{
id: postDetails?.[0]?.id,
postId: pId,
releaseURL: `https://www.pinterest.com/pin/${pId}`,
status: 'success',
},
];
} catch (err) {
console.log(err);
return [];
}
}
async analytics(
id: string,
accessToken: string,
date: number
): Promise<AnalyticsData[]> {
const until = dayjs().format('YYYY-MM-DD');
const since = dayjs().subtract(date, 'day').format('YYYY-MM-DD');
const {
all: { daily_metrics },
} = await (
await this.fetch(
`https://api.pinterest.com/v5/user_account/analytics?start_date=${since}&end_date=${until}`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
}
)
).json();
return daily_metrics.reduce(
(acc: any, item: any) => {
if (typeof item.metrics.PIN_CLICK_RATE !== 'undefined') {
acc[0].data.push({
date: item.date,
total: item.metrics.PIN_CLICK_RATE,
});
acc[1].data.push({
date: item.date,
total: item.metrics.IMPRESSION,
});
acc[2].data.push({
date: item.date,
total: item.metrics.PIN_CLICK,
});
acc[3].data.push({
date: item.date,
total: item.metrics.ENGAGEMENT,
});
acc[4].data.push({
date: item.date,
total: item.metrics.SAVE,
});
}
return acc;
},
[
{ label: 'Pin click rate', data: [] as any[] },
{ label: 'Impressions', data: [] as any[] },
{ label: 'Pin Clicks', data: [] as any[] },
{ label: 'Engagement', data: [] as any[] },
{ label: 'Saves', data: [] as any[] },
]
);
}
}