From b8951abd910c01383b27decf3e1c7f70a0405a55 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Sun, 5 Jan 2025 18:58:43 +0700 Subject: [PATCH] feat: instagram standalone option --- .../icons/platforms/instagram-standalone.png | Bin 0 -> 17438 bytes .../launches/add.provider.component.tsx | 28 +++- .../launches/providers/show.all.providers.tsx | 1 + .../src/integrations/integration.manager.ts | 5 +- .../integrations/social/instagram.provider.ts | 26 ++-- .../social/instagram.standalone.provider.ts | 130 ++++++++++++++++++ .../social/social.integrations.interface.ts | 1 + .../src/integrations/social/x.provider.ts | 15 +- 8 files changed, 189 insertions(+), 17 deletions(-) create mode 100644 apps/frontend/public/icons/platforms/instagram-standalone.png create mode 100644 libraries/nestjs-libraries/src/integrations/social/instagram.standalone.provider.ts diff --git a/apps/frontend/public/icons/platforms/instagram-standalone.png b/apps/frontend/public/icons/platforms/instagram-standalone.png new file mode 100644 index 0000000000000000000000000000000000000000..389d7eb5efbddeee1f0a26a9de0de70e9db0a322 GIT binary patch literal 17438 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 v0
@@ -458,7 +465,24 @@ export const AddProviderComponent: FC<{ /> )}
-
{item.name}
+
+ {item.name} + {!!item.toolTip && ( + + + + )} +
))} diff --git a/apps/frontend/src/components/launches/providers/show.all.providers.tsx b/apps/frontend/src/components/launches/providers/show.all.providers.tsx index 2549ca36..34afbfe8 100644 --- a/apps/frontend/src/components/launches/providers/show.all.providers.tsx +++ b/apps/frontend/src/components/launches/providers/show.all.providers.tsx @@ -29,6 +29,7 @@ export const Providers = [ {identifier: 'hashnode', component: HashnodeProvider}, {identifier: 'facebook', component: FacebookProvider}, {identifier: 'instagram', component: InstagramProvider}, + {identifier: 'instagram-standalone', component: InstagramProvider}, {identifier: 'youtube', component: YoutubeProvider}, {identifier: 'tiktok', component: TiktokProvider}, {identifier: 'pinterest', component: PinterestProvider}, diff --git a/libraries/nestjs-libraries/src/integrations/integration.manager.ts b/libraries/nestjs-libraries/src/integrations/integration.manager.ts index bf44d558..8dd99931 100644 --- a/libraries/nestjs-libraries/src/integrations/integration.manager.ts +++ b/libraries/nestjs-libraries/src/integrations/integration.manager.ts @@ -22,6 +22,7 @@ import { SlackProvider } from '@gitroom/nestjs-libraries/integrations/social/sla import { MastodonProvider } from '@gitroom/nestjs-libraries/integrations/social/mastodon.provider'; import { BlueskyProvider } from '@gitroom/nestjs-libraries/integrations/social/bluesky.provider'; import { LemmyProvider } from '@gitroom/nestjs-libraries/integrations/social/lemmy.provider'; +import { InstagramStandaloneProvider } from '@gitroom/nestjs-libraries/integrations/social/instagram.standalone.provider'; // import { MastodonCustomProvider } from '@gitroom/nestjs-libraries/integrations/social/mastodon.custom.provider'; const socialIntegrationList: SocialProvider[] = [ @@ -29,8 +30,9 @@ const socialIntegrationList: SocialProvider[] = [ new LinkedinProvider(), new LinkedinPageProvider(), new RedditProvider(), - new FacebookProvider(), new InstagramProvider(), + new InstagramStandaloneProvider(), + new FacebookProvider(), new ThreadsProvider(), new YoutubeProvider(), new TiktokProvider(), @@ -58,6 +60,7 @@ export class IntegrationManager { socialIntegrationList.map(async (p) => ({ name: p.name, identifier: p.identifier, + toolTip: p.toolTip, isExternal: !!p.externalUrl, ...(p.customFields ? { customFields: await p.customFields() } : {}), })) diff --git a/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts b/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts index 69d9ab23..fd787a92 100644 --- a/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts @@ -10,14 +10,16 @@ import { timer } from '@gitroom/helpers/utils/timer'; import dayjs from 'dayjs'; import { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract'; import { InstagramDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/instagram.dto'; +import { Integration } from '@prisma/client'; export class InstagramProvider extends SocialAbstract implements SocialProvider { identifier = 'instagram'; - name = 'Instagram'; + name = 'Instagram\n(Facebook Business)'; isBetweenSteps = true; + toolTip = 'Instagram must be business and connected to a Facebook page'; scopes = [ 'instagram_basic', 'pages_show_list', @@ -204,7 +206,9 @@ export class InstagramProvider async post( id: string, accessToken: string, - postDetails: PostDetails[] + postDetails: PostDetails[], + integration: Integration, + type = 'graph.facebook.com' ): Promise { const [firstPost, ...theRest] = postDetails; console.log('in progress'); @@ -241,7 +245,7 @@ export class InstagramProvider console.log(collaborators); const { id: photoId } = await ( await this.fetch( - `https://graph.facebook.com/v20.0/${id}/media?${mediaType}${isCarousel}${collaborators}&access_token=${accessToken}${caption}`, + `https://${type}/v20.0/${id}/media?${mediaType}${isCarousel}${collaborators}&access_token=${accessToken}${caption}`, { method: 'POST', } @@ -253,7 +257,7 @@ export class InstagramProvider while (status === 'IN_PROGRESS') { const { status_code } = await ( await this.fetch( - `https://graph.facebook.com/v20.0/${photoId}?access_token=${accessToken}&fields=status_code` + `https://${type}/v20.0/${photoId}?access_token=${accessToken}&fields=status_code` ) ).json(); await timer(3000); @@ -272,7 +276,7 @@ export class InstagramProvider if (medias.length === 1) { const { id: mediaId } = await ( await this.fetch( - `https://graph.facebook.com/v20.0/${id}/media_publish?creation_id=${medias[0]}&access_token=${accessToken}&field=id`, + `https://${type}/v20.0/${id}/media_publish?creation_id=${medias[0]}&access_token=${accessToken}&field=id`, { method: 'POST', } @@ -283,7 +287,7 @@ export class InstagramProvider const { permalink } = await ( await this.fetch( - `https://graph.facebook.com/v20.0/${mediaId}?fields=permalink&access_token=${accessToken}` + `https://${type}/v20.0/${mediaId}?fields=permalink&access_token=${accessToken}` ) ).json(); @@ -298,7 +302,7 @@ export class InstagramProvider } else { const { id: containerId, ...all3 } = await ( await this.fetch( - `https://graph.facebook.com/v20.0/${id}/media?caption=${encodeURIComponent( + `https://${type}/v20.0/${id}/media?caption=${encodeURIComponent( firstPost?.message )}&media_type=CAROUSEL&children=${encodeURIComponent( medias.join(',') @@ -313,7 +317,7 @@ export class InstagramProvider while (status === 'IN_PROGRESS') { const { status_code } = await ( await this.fetch( - `https://graph.facebook.com/v20.0/${containerId}?fields=status_code&access_token=${accessToken}` + `https://${type}/v20.0/${containerId}?fields=status_code&access_token=${accessToken}` ) ).json(); await timer(3000); @@ -322,7 +326,7 @@ export class InstagramProvider const { id: mediaId, ...all4 } = await ( await this.fetch( - `https://graph.facebook.com/v20.0/${id}/media_publish?creation_id=${containerId}&access_token=${accessToken}&field=id`, + `https://${type}/v20.0/${id}/media_publish?creation_id=${containerId}&access_token=${accessToken}&field=id`, { method: 'POST', } @@ -333,7 +337,7 @@ export class InstagramProvider const { permalink } = await ( await this.fetch( - `https://graph.facebook.com/v20.0/${mediaId}?fields=permalink&access_token=${accessToken}` + `https://${type}/v20.0/${mediaId}?fields=permalink&access_token=${accessToken}` ) ).json(); @@ -350,7 +354,7 @@ export class InstagramProvider for (const post of theRest) { const { id: commentId } = await ( await this.fetch( - `https://graph.facebook.com/v20.0/${containerIdGlobal}/comments?message=${encodeURIComponent( + `https://${type}/v20.0/${containerIdGlobal}/comments?message=${encodeURIComponent( post.message )}&access_token=${accessToken}`, { diff --git a/libraries/nestjs-libraries/src/integrations/social/instagram.standalone.provider.ts b/libraries/nestjs-libraries/src/integrations/social/instagram.standalone.provider.ts new file mode 100644 index 00000000..f1c3cd7b --- /dev/null +++ b/libraries/nestjs-libraries/src/integrations/social/instagram.standalone.provider.ts @@ -0,0 +1,130 @@ +import { + AuthTokenDetails, + PostDetails, + PostResponse, + SocialProvider, +} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface'; +import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; +import dayjs from 'dayjs'; +import { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract'; +import { InstagramDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/instagram.dto'; +import { InstagramProvider } from '@gitroom/nestjs-libraries/integrations/social/instagram.provider'; +import { Integration } from '@prisma/client'; + +const instagramProvider = new InstagramProvider(); + +export class InstagramStandaloneProvider + extends SocialAbstract + implements SocialProvider +{ + identifier = 'instagram-standalone'; + name = 'Instagram\n(Standalone)'; + isBetweenSteps = false; + scopes = [ + 'instagram_business_basic', + 'instagram_business_content_publish', + 'instagram_business_manage_comments', + ]; + toolTip = 'Standalone does not support insights or tagging'; + + async refreshToken(refresh_token: string): Promise { + return { + refreshToken: '', + expiresIn: 0, + accessToken: '', + id: '', + name: '', + picture: '', + username: '', + }; + } + + async generateAuthUrl() { + const state = makeId(6); + return { + url: + `https://www.instagram.com/oauth/authorize?enable_fb_login=0&client_id=${ + process.env.INSTAGRAM_APP_ID + }&redirect_uri=${encodeURIComponent( + `${ + process?.env.FRONTEND_URL?.indexOf('https') == -1 + ? `https://redirectmeto.com/${process?.env.FRONTEND_URL}` + : `${process?.env.FRONTEND_URL}` + }/integrations/social/instagram-standalone` + )}&response_type=code&scope=${encodeURIComponent( + this.scopes.join(',') + )}` + `&state=${state}`, + codeVerifier: makeId(10), + state, + }; + } + + async authenticate(params: { + code: string; + codeVerifier: string; + refresh: string; + }) { + const formData = new FormData(); + formData.append('client_id', process.env.INSTAGRAM_APP_ID!); + formData.append('client_secret', process.env.INSTAGRAM_APP_SECRET!); + formData.append('grant_type', 'authorization_code'); + formData.append( + 'redirect_uri', + `${ + process?.env.FRONTEND_URL?.indexOf('https') == -1 + ? `https://redirectmeto.com/${process?.env.FRONTEND_URL}` + : `${process?.env.FRONTEND_URL}` + }/integrations/social/instagram-standalone` + ); + formData.append('code', params.code); + + const getAccessToken = await ( + await this.fetch('https://api.instagram.com/oauth/access_token', { + method: 'POST', + body: formData, + }) + ).json(); + + const { access_token, expires_in, ...all } = await ( + await this.fetch( + 'https://graph.instagram.com/access_token' + + '?grant_type=ig_exchange_token' + + `&client_id=${process.env.INSTAGRAM_APP_ID}` + + `&client_secret=${process.env.INSTAGRAM_APP_SECRET}` + + `&access_token=${getAccessToken.access_token}` + ) + ).json(); + + this.checkScopes(this.scopes, getAccessToken.permissions); + + const { + user_id, + name, + username, + profile_picture_url, + } = await ( + await this.fetch( + `https://graph.instagram.com/v21.0/me?fields=user_id,username,name,profile_picture_url&access_token=${access_token}` + ) + ).json(); + + return { + id: user_id, + name, + accessToken: access_token, + refreshToken: access_token, + expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(), + picture: profile_picture_url, + username, + }; + } + + async post( + id: string, + accessToken: string, + postDetails: PostDetails[], + integration: Integration + ): Promise { + return instagramProvider.post(id, accessToken, postDetails, integration, 'graph.instagram.com'); + } +} diff --git a/libraries/nestjs-libraries/src/integrations/social/social.integrations.interface.ts b/libraries/nestjs-libraries/src/integrations/social/social.integrations.interface.ts index bd959942..44b19bfa 100644 --- a/libraries/nestjs-libraries/src/integrations/social/social.integrations.interface.ts +++ b/libraries/nestjs-libraries/src/integrations/social/social.integrations.interface.ts @@ -120,6 +120,7 @@ export interface SocialProvider }[] >; name: string; + toolTip?: string; oneTimeToken?: boolean; isBetweenSteps: boolean; scopes: string[]; diff --git a/libraries/nestjs-libraries/src/integrations/social/x.provider.ts b/libraries/nestjs-libraries/src/integrations/social/x.provider.ts index 0b2a9569..0f9ce41c 100644 --- a/libraries/nestjs-libraries/src/integrations/social/x.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/x.provider.ts @@ -20,6 +20,8 @@ export class XProvider extends SocialAbstract implements SocialProvider { name = 'X'; isBetweenSteps = false; scopes = []; + toolTip = + 'You will be logged in into your current account, if you would like a different account, change it first on X'; @Plug({ identifier: 'x-autoRepostPost', @@ -199,13 +201,20 @@ export class XProvider extends SocialAbstract implements SocialProvider { accessSecret: oauth_token_secret, }); - const { accessToken, client, accessSecret } = - await startingClient.login(code); + const { accessToken, client, accessSecret } = await startingClient.login( + code + ); const { data: { username, verified, profile_image_url, name, id }, } = await client.v2.me({ - 'user.fields': ['username', 'verified', 'verified_type', 'profile_image_url', 'name'], + 'user.fields': [ + 'username', + 'verified', + 'verified_type', + 'profile_image_url', + 'name', + ], }); return {