diff --git a/apps/frontend/src/components/launches/providers/high.order.provider.tsx b/apps/frontend/src/components/launches/providers/high.order.provider.tsx index 6d1d9bee..7a5e6e87 100644 --- a/apps/frontend/src/components/launches/providers/high.order.provider.tsx +++ b/apps/frontend/src/components/launches/providers/high.order.provider.tsx @@ -382,8 +382,8 @@ export const withProvider = ( , document.querySelector('#renderEditor')! )} - {showTab === 2 && ( -
+ {(showTab === 0 || showTab === 2) && ( +
)} diff --git a/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx b/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx index 8aabc469..0da127c7 100644 --- a/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx +++ b/apps/frontend/src/components/launches/providers/tiktok/tiktok.provider.tsx @@ -8,6 +8,133 @@ import { linkedinCompanyPreventRemove, } from '@gitroom/helpers/utils/linkedin.company.prevent.remove'; import { VideoOrImage } from '@gitroom/react/helpers/video.or.image'; +import { TikTokDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/tiktok.dto'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; +import { Select } from '@gitroom/react/form/select'; + +const privacyLevel = [ + { + value: 'PUBLIC_TO_EVERYONE', + label: 'Public to everyone', + }, + { + value: 'MUTUAL_FOLLOW_FRIENDS', + label: 'Mutual follow friends', + }, + { + value: 'FOLLOWER_OF_CREATOR', + label: 'Follower of creator', + }, + { + value: 'SELF_ONLY', + label: 'Self only', + }, +]; + +const yesNo = [ + { + value: 'true', + label: 'Yes', + }, + { + value: 'false', + label: 'No', + }, +]; + +const TikTokSettings: FC = () => { + const { register, control } = useSettings(); + return ( +
+ + + + + + + + + +
+ ); +}; const TikTokPreview: FC = (props) => { const { value: topValue, integration } = useIntegration(); @@ -110,4 +237,4 @@ const TikTokPreview: FC = (props) => { ); }; -export default withProvider(null, TikTokPreview); +export default withProvider(TikTokSettings, TikTokPreview, TikTokDto); diff --git a/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts index 563b4c65..20d813c4 100644 --- a/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts +++ b/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts @@ -11,6 +11,7 @@ import {RedditSettingsDto} from "@gitroom/nestjs-libraries/dtos/posts/providers- import { YoutubeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/youtube.settings.dto'; import { PinterestSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/pinterest.dto'; import { DribbbleDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dribbble.dto'; +import { TikTokDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/tiktok.dto'; export class EmptySettings {} export class Integration { @@ -66,6 +67,7 @@ export class Post { { value: YoutubeSettingsDto, name: 'youtube' }, { value: PinterestSettingsDto, name: 'pinterest' }, { value: DribbbleDto, name: 'dribbble' }, + { value: TikTokDto, name: 'tiktok' }, ], }, }) diff --git a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/all.providers.settings.ts b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/all.providers.settings.ts index 042e3ad7..2f6ad04c 100644 --- a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/all.providers.settings.ts +++ b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/all.providers.settings.ts @@ -4,6 +4,7 @@ import { HashnodeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/provid import { RedditSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/reddit.dto'; import { PinterestSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/pinterest.dto'; import { YoutubeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/youtube.settings.dto'; +import { TikTokDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/tiktok.dto'; export type AllProvidersSettings = | DevToSettingsDto @@ -11,4 +12,5 @@ export type AllProvidersSettings = | HashnodeSettingsDto | RedditSettingsDto | YoutubeSettingsDto - | PinterestSettingsDto; + | PinterestSettingsDto + | TikTokDto; diff --git a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/tiktok.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/tiktok.dto.ts new file mode 100644 index 00000000..8bd53412 --- /dev/null +++ b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/tiktok.dto.ts @@ -0,0 +1,22 @@ +import { IsBoolean, IsIn, IsString } from 'class-validator'; + +export class TikTokDto { + @IsIn(['PUBLIC_TO_EVERYONE', 'MUTUAL_FOLLOW_FRIENDS', 'FOLLOWER_OF_CREATOR', 'SELF_ONLY']) + @IsString() + privacy_level: 'PUBLIC_TO_EVERYONE' | 'MUTUAL_FOLLOW_FRIENDS' | 'FOLLOWER_OF_CREATOR' | 'SELF_ONLY'; + + @IsBoolean() + disable_duet: boolean; + + @IsBoolean() + disable_stitch: boolean; + + @IsBoolean() + disable_comment: boolean; + + @IsBoolean() + brand_content_toggle: boolean; + + @IsBoolean() + brand_organic_toggle: boolean; +} diff --git a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts index de634367..ace3bce3 100644 --- a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts @@ -4,7 +4,6 @@ import { 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'; @@ -13,57 +12,71 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider { name = 'Tiktok'; isBetweenSteps = false; - async refreshToken(refresh_token: string): Promise { + async refreshToken(refreshToken: string): Promise { + const value = { + client_key: process.env.TIKTOK_CLIENT_ID!, + client_secret: process.env.TIKTOK_CLIENT_SECRET!, + grant_type: 'authorization_code', + refresh_token: refreshToken, + }; + + const { access_token, refresh_token } = await ( + await this.fetch('https://open.tiktokapis.com/v2/oauth/token/', { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + method: 'POST', + body: new URLSearchParams(value).toString(), + }) + ).json(); + + const { + data: { + user: { avatar_url, display_name, open_id }, + }, + } = await ( + await fetch( + 'https://open.tiktokapis.com/v2/user/info/?fields=open_id,avatar_url,display_name', + { + method: 'GET', + headers: { + Authorization: `Bearer ${access_token}`, + }, + } + ) + ).json(); + return { - refreshToken: '', - expiresIn: 0, - accessToken: '', - id: '', - name: '', - picture: '', - username: '', + refreshToken: refresh_token, + expiresIn: dayjs().add(23, 'hours').unix() - dayjs().unix(), + accessToken: access_token, + id: open_id.replace(/-/g, ''), + name: display_name, + picture: avatar_url, + username: display_name.toLowerCase(), }; } async generateAuthUrl(refresh?: string) { - const state = makeId(6); - console.log( - 'https://www.tiktok.com/v2/auth/authorize' + - `?client_key=${process.env.TIKTOK_CLIENT_ID}` + - `&redirect_uri=${encodeURIComponent( - `${ - process.env.NODE_ENV === 'development' || !process.env.NODE_ENV - ? 'https://redirectmeto.com/' - : '' - }${process.env.FRONTEND_URL}/integrations/social/tiktok${ - refresh ? `?refresh=${refresh}` : '' - }` - )}` + - `&state=${state}` + - `&response_type=code` + - `&scope=${encodeURIComponent( - 'user.info.basic,video.publish,video.upload' - )}` - ); + const state = Math.random().toString(36).substring(2); + return { url: - 'https://www.tiktok.com/v2/auth/authorize' + + 'https://www.tiktok.com/v2/auth/authorize/' + `?client_key=${process.env.TIKTOK_CLIENT_ID}` + `&redirect_uri=${encodeURIComponent( `${ process.env.NODE_ENV === 'development' || !process.env.NODE_ENV - ? 'https://redirectmeto.com/' - : '' - }${process.env.FRONTEND_URL}/integrations/social/tiktok${ - refresh ? `?refresh=${refresh}` : '' - }` + ? `https://integration.git.sn/integrations/social/tiktok` + : `${process.env.FRONTEND_URL}/integrations/social/tiktok` + }${refresh ? `?refresh=${refresh}` : ''}` )}` + `&state=${state}` + `&response_type=code` + `&scope=${encodeURIComponent( 'user.info.basic,video.publish,video.upload' )}`, - codeVerifier: makeId(10), + codeVerifier: state, state, }; } @@ -73,15 +86,52 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider { codeVerifier: string; refresh?: string; }) { - console.log(params); + const value = { + client_key: process.env.TIKTOK_CLIENT_ID!, + client_secret: process.env.TIKTOK_CLIENT_SECRET!, + code: params.code, + grant_type: 'authorization_code', + code_verifier: params.codeVerifier, + redirect_uri: + process.env.NODE_ENV === 'development' || !process.env.NODE_ENV + ? `https://integration.git.sn/integrations/social/tiktok` + : `${process.env.FRONTEND_URL}/integrations/social/tiktok`, + }; + + const { access_token, refresh_token } = await ( + await this.fetch('https://open.tiktokapis.com/v2/oauth/token/', { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + method: 'POST', + body: new URLSearchParams(value).toString(), + }) + ).json(); + + const { + data: { + user: { avatar_url, display_name, open_id }, + }, + } = await ( + await fetch( + 'https://open.tiktokapis.com/v2/user/info/?fields=open_id,avatar_url,display_name', + { + method: 'GET', + headers: { + Authorization: `Bearer ${access_token}`, + }, + } + ) + ).json(); + return { - id: '', - name: '', - accessToken: '', - refreshToken: '', - expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(), - picture: '', - username: '', + id: open_id.replace(/-/g, ''), + name: display_name, + accessToken: access_token, + refreshToken: refresh_token, + expiresIn: dayjs().add(23, 'hours').unix() - dayjs().unix(), + picture: avatar_url, + username: display_name.toLowerCase(), }; }