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 00000000..389d7eb5
Binary files /dev/null and b/apps/frontend/public/icons/platforms/instagram-standalone.png differ
diff --git a/apps/frontend/src/components/launches/add.provider.component.tsx b/apps/frontend/src/components/launches/add.provider.component.tsx
index 2cdbcb30..7d9c6c12 100644
--- a/apps/frontend/src/components/launches/add.provider.component.tsx
+++ b/apps/frontend/src/components/launches/add.provider.component.tsx
@@ -305,6 +305,7 @@ export const AddProviderComponent: FC<{
social: Array<{
identifier: string;
name: string;
+ toolTip?: string;
isExternal: boolean;
customFields?: Array<{
key: string;
@@ -444,8 +445,14 @@ export const AddProviderComponent: FC<{
item.isExternal,
item.customFields
)}
+ {...(!!item.toolTip
+ ? {
+ 'data-tooltip-id': 'tooltip',
+ 'data-tooltip-content': item.toolTip,
+ }
+ : {})}
className={
- 'w-[120px] h-[100px] bg-input text-textColor justify-center items-center flex flex-col gap-[10px] cursor-pointer'
+ 'w-[200px] h-[100px] text-[14px] bg-input text-textColor relative justify-center items-center flex flex-col gap-[10px] cursor-pointer'
}
>
@@ -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 {