-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
instagram stories, add collaborators, infrastructure for settings in validation #468
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,7 +8,10 @@ const finalInformation = {} as { | |||||||||||||||||
| settings: () => object; | ||||||||||||||||||
| trigger: () => Promise<boolean>; | ||||||||||||||||||
| isValid: boolean; | ||||||||||||||||||
| checkValidity?: (value: Array<Array<{path: string}>>) => Promise<string|true>; | ||||||||||||||||||
| checkValidity?: ( | ||||||||||||||||||
| value: Array<Array<{ path: string }>>, | ||||||||||||||||||
| settings: any | ||||||||||||||||||
| ) => Promise<string | true>; | ||||||||||||||||||
| maximumCharacters?: number; | ||||||||||||||||||
| }; | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
@@ -18,8 +21,11 @@ export const useValues = ( | |||||||||||||||||
| identifier: string, | ||||||||||||||||||
| value: Array<{ id?: string; content: string; media?: Array<string> }>, | ||||||||||||||||||
| dto: any, | ||||||||||||||||||
| checkValidity?: (value: Array<Array<{path: string}>>) => Promise<string|true>, | ||||||||||||||||||
| maximumCharacters?: number, | ||||||||||||||||||
| checkValidity?: ( | ||||||||||||||||||
| value: Array<Array<{ path: string }>>, | ||||||||||||||||||
| settings: any | ||||||||||||||||||
| ) => Promise<string | true>, | ||||||||||||||||||
|
Comment on lines
+24
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Apply the same generic type parameter here for consistency. For consistency with the type definition above, apply the same generic type parameter here. - checkValidity?: (
- value: Array<Array<{ path: string }>>,
- settings: any
- ) => Promise<string | true>,
+ checkValidity?: <T>(
+ value: Array<Array<{ path: string }>>,
+ settings: T
+ ) => Promise<string | true>,📝 Committable suggestion
Suggested change
|
||||||||||||||||||
| maximumCharacters?: number | ||||||||||||||||||
| ) => { | ||||||||||||||||||
| const resolver = useMemo(() => { | ||||||||||||||||||
| return classValidatorResolver(dto); | ||||||||||||||||||
|
|
@@ -43,8 +49,7 @@ export const useValues = ( | |||||||||||||||||
| finalInformation[integration].trigger = form.trigger; | ||||||||||||||||||
|
|
||||||||||||||||||
| if (checkValidity) { | ||||||||||||||||||
| finalInformation[integration].checkValidity = | ||||||||||||||||||
| checkValidity; | ||||||||||||||||||
| finalInformation[integration].checkValidity = checkValidity; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| if (maximumCharacters) { | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,89 @@ | ||||||||||||||
| import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; | ||||||||||||||
| import { FC } from 'react'; | ||||||||||||||
| import { Select } from '@gitroom/react/form/select'; | ||||||||||||||
| import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; | ||||||||||||||
| import { InstagramDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/instagram.dto'; | ||||||||||||||
| import { InstagramCollaboratorsTags } from '@gitroom/frontend/components/launches/providers/instagram/instagram.tags'; | ||||||||||||||
|
|
||||||||||||||
| const postType = [ | ||||||||||||||
| { | ||||||||||||||
| value: 'post', | ||||||||||||||
| label: 'Post / Reel', | ||||||||||||||
| }, | ||||||||||||||
| { | ||||||||||||||
| value: 'story', | ||||||||||||||
| label: 'Story', | ||||||||||||||
| }, | ||||||||||||||
| ]; | ||||||||||||||
| const InstagramCollaborators: FC<{ values?: any }> = (props) => { | ||||||||||||||
| const { watch, register, formState, control } = useSettings(); | ||||||||||||||
Check warningCode scanning / ESLint Disallow unused variables Warning
'formState' is assigned a value but never used.
Check warningCode scanning / ESLint Disallow unused variables Warning
'control' is assigned a value but never used.
|
||||||||||||||
| const postCurrentType = watch('post_type'); | ||||||||||||||
| return ( | ||||||||||||||
| <> | ||||||||||||||
| <Select | ||||||||||||||
| label="Post Type" | ||||||||||||||
| {...register('post_type', { | ||||||||||||||
| value: 'post', | ||||||||||||||
| })} | ||||||||||||||
| > | ||||||||||||||
| <option value="">Select Post Type...</option> | ||||||||||||||
| {postType.map((item) => ( | ||||||||||||||
| <option key={item.value} value={item.value}> | ||||||||||||||
| {item.label} | ||||||||||||||
| </option> | ||||||||||||||
| ))} | ||||||||||||||
| </Select> | ||||||||||||||
|
|
||||||||||||||
| {postCurrentType !== 'story' && ( | ||||||||||||||
| <InstagramCollaboratorsTags | ||||||||||||||
| label="Collaborators (max 3) - accounts can't be private" | ||||||||||||||
| {...register('collaborators')} | ||||||||||||||
| /> | ||||||||||||||
| )} | ||||||||||||||
| </> | ||||||||||||||
| ); | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| export default withProvider<InstagramDto>( | ||||||||||||||
| InstagramCollaborators, | ||||||||||||||
| undefined, | ||||||||||||||
| InstagramDto, | ||||||||||||||
| async ([firstPost, ...otherPosts], settings) => { | ||||||||||||||
| if (!firstPost.length) { | ||||||||||||||
| return 'Instagram should have at least one media'; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (firstPost.length > 1 && settings.post_type === 'story') { | ||||||||||||||
| return 'Instagram stories can only have one media'; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const checkVideosLength = await Promise.all( | ||||||||||||||
| firstPost | ||||||||||||||
| .filter((f) => f.path.indexOf('mp4') > -1) | ||||||||||||||
| .flatMap((p) => p.path) | ||||||||||||||
| .map((p) => { | ||||||||||||||
|
Comment on lines
+62
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct the usage of The use of Apply this diff to correct the code: - .filter((f) => f.path.indexOf('mp4') > -1)
+ .filter((f) => /\.(mp4|mov|avi)$/i.test(f.path))
- .flatMap((p) => p.path)
+ .map((p) => p.path)📝 Committable suggestion
Suggested change
|
||||||||||||||
| return new Promise<number>((res) => { | ||||||||||||||
| const video = document.createElement('video'); | ||||||||||||||
| video.preload = 'metadata'; | ||||||||||||||
| video.src = p; | ||||||||||||||
| video.addEventListener('loadedmetadata', () => { | ||||||||||||||
| res(video.duration); | ||||||||||||||
| }); | ||||||||||||||
| }); | ||||||||||||||
| }) | ||||||||||||||
| ); | ||||||||||||||
|
|
||||||||||||||
| for (const video of checkVideosLength) { | ||||||||||||||
| if (video > 60 && settings.post_type === 'story') { | ||||||||||||||
| return 'Instagram stories should be maximum 60 seconds'; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (video > 90 && settings.post_type === 'post') { | ||||||||||||||
| return 'Instagram reel should be maximum 90 seconds'; | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return true; | ||||||||||||||
| }, | ||||||||||||||
| 2200 | ||||||||||||||
| ); | ||||||||||||||
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,61 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import { FC, useCallback, useEffect, useMemo, useState } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { ReactTags } from 'react-tag-autocomplete'; | ||||||||||||||||||||||||||||||||||||||||||||||
| import interClass from '@gitroom/react/helpers/inter.font'; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export const InstagramCollaboratorsTags: FC<{ | ||||||||||||||||||||||||||||||||||||||||||||||
| name: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| label: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| onChange: (event: { target: { value: any[]; name: string } }) => void; | ||||||||||||||||||||||||||||||||||||||||||||||
| }> = (props) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const { onChange, name, label } = props; | ||||||||||||||||||||||||||||||||||||||||||||||
| const { getValues } = useSettings(); | ||||||||||||||||||||||||||||||||||||||||||||||
| const [tagValue, setTagValue] = useState<any[]>([]); | ||||||||||||||||||||||||||||||||||||||||||||||
| const [suggestions, setSuggestions] = useState<string>(''); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const onDelete = useCallback( | ||||||||||||||||||||||||||||||||||||||||||||||
| (tagIndex: number) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const modify = tagValue.filter((_, i) => i !== tagIndex); | ||||||||||||||||||||||||||||||||||||||||||||||
| setTagValue(modify); | ||||||||||||||||||||||||||||||||||||||||||||||
| onChange({ target: { value: modify, name } }); | ||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||
| [tagValue] | ||||||||||||||||||||||||||||||||||||||||||||||
Check warningCode scanning / ESLint verifies the list of dependencies for Hooks like useEffect and similar Warning
React Hook useCallback has missing dependencies: 'name' and 'onChange'. Either include them or remove the dependency array.
|
||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+16
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Include missing dependencies in The Apply this diff: - [tagValue]
+ [tagValue, name, onChange]📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Check: ESLint[warning] 22-22: verifies the list of dependencies for Hooks like useEffect and similar |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const onAddition = useCallback( | ||||||||||||||||||||||||||||||||||||||||||||||
| (newTag: any) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (tagValue.length >= 3) { | ||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| const modify = [...tagValue, newTag]; | ||||||||||||||||||||||||||||||||||||||||||||||
| setTagValue(modify); | ||||||||||||||||||||||||||||||||||||||||||||||
| onChange({ target: { value: modify, name } }); | ||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||
| [tagValue] | ||||||||||||||||||||||||||||||||||||||||||||||
Check warningCode scanning / ESLint verifies the list of dependencies for Hooks like useEffect and similar Warning
React Hook useCallback has missing dependencies: 'name' and 'onChange'. Either include them or remove the dependency array.
|
||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+25
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Include missing dependencies in The Apply this diff: - [tagValue]
+ [tagValue, name, onChange]📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Check: ESLint[warning] 34-34: verifies the list of dependencies for Hooks like useEffect and similar |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const settings = getValues()[props.name]; | ||||||||||||||||||||||||||||||||||||||||||||||
| if (settings) { | ||||||||||||||||||||||||||||||||||||||||||||||
| setTagValue(settings); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||||||||||||||||||||||||
Check warningCode scanning / ESLint verifies the list of dependencies for Hooks like useEffect and similar Warning
React Hook useEffect has missing dependencies: 'getValues' and 'props.name'. Either include them or remove the dependency array.
Comment on lines
+37
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Include missing dependencies in The Apply this diff: - }, []);
+ }, [getValues, name]);
🧰 Tools🪛 GitHub Check: ESLint[warning] 42-42: verifies the list of dependencies for Hooks like useEffect and similar |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const suggestionsArray = useMemo(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| return [...tagValue, { label: suggestions, value: suggestions }].filter(f => f.label); | ||||||||||||||||||||||||||||||||||||||||||||||
| }, [suggestions, tagValue]); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||||||||||||||||||||
| <div className={`${interClass} text-[14px] mb-[6px]`}>{label}</div> | ||||||||||||||||||||||||||||||||||||||||||||||
| <ReactTags | ||||||||||||||||||||||||||||||||||||||||||||||
| placeholderText="Add a tag" | ||||||||||||||||||||||||||||||||||||||||||||||
| suggestions={suggestionsArray} | ||||||||||||||||||||||||||||||||||||||||||||||
| selected={tagValue} | ||||||||||||||||||||||||||||||||||||||||||||||
| onAdd={onAddition} | ||||||||||||||||||||||||||||||||||||||||||||||
| onInput={setSuggestions} | ||||||||||||||||||||||||||||||||||||||||||||||
| onDelete={onDelete} | ||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { Type } from 'class-transformer'; | ||
| import { IsArray, IsDefined, IsIn, IsString, ValidateNested } from 'class-validator'; | ||
|
|
||
| export class Collaborators { | ||
| @IsDefined() | ||
| @IsString() | ||
| label: string; | ||
| } | ||
| export class InstagramDto { | ||
| @IsIn(['post', 'story']) | ||
| @IsDefined() | ||
| post_type: 'post' | 'story'; | ||
|
|
||
| @Type(() => Collaborators) | ||
| @ValidateNested({ each: true }) | ||
| @IsArray() | ||
| collaborators: Collaborators[]; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; | |
| 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'; | ||
|
|
||
| export class InstagramProvider | ||
| extends SocialAbstract | ||
|
|
@@ -203,10 +204,11 @@ export class InstagramProvider | |
| async post( | ||
| id: string, | ||
| accessToken: string, | ||
| postDetails: PostDetails[] | ||
| postDetails: PostDetails<InstagramDto>[] | ||
| ): Promise<PostResponse[]> { | ||
| const [firstPost, ...theRest] = postDetails; | ||
|
|
||
| console.log('in progress'); | ||
| const isStory = firstPost.settings.post_type === 'story'; | ||
| const medias = await Promise.all( | ||
| firstPost?.media?.map(async (m) => { | ||
| const caption = | ||
|
|
@@ -218,18 +220,34 @@ export class InstagramProvider | |
| const mediaType = | ||
| m.url.indexOf('.mp4') > -1 | ||
| ? firstPost?.media?.length === 1 | ||
| ? `video_url=${m.url}&media_type=REELS` | ||
| ? isStory | ||
| ? `video_url=${m.url}&media_type=STORIES` | ||
| : `video_url=${m.url}&media_type=REELS` | ||
| : isStory | ||
| ? `video_url=${m.url}&media_type=STORIES` | ||
| : `video_url=${m.url}&media_type=VIDEO` | ||
| : isStory | ||
| ? `image_url=${m.url}&media_type=STORIES` | ||
| : `image_url=${m.url}`; | ||
|
Comment on lines
+223
to
231
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Refactor complex media type logic for better readability. The nested ternary operations make the code hard to read and maintain. Consider extracting this logic into a separate function. + private getMediaType(url: string, isStory: boolean, isMultiMedia: boolean): string {
+ const isVideo = url.indexOf('.mp4') > -1;
+ if (!isVideo) {
+ return isStory ? `image_url=${url}&media_type=STORIES` : `image_url=${url}`;
+ }
+ if (isStory) {
+ return `video_url=${url}&media_type=STORIES`;
+ }
+ if (!isMultiMedia) {
+ return `video_url=${url}&media_type=REELS`;
+ }
+ return `video_url=${url}&media_type=VIDEO`;
+ }Then use it in the code: - const mediaType =
- m.url.indexOf('.mp4') > -1
- ? firstPost?.media?.length === 1
- ? isStory
- ? `video_url=${m.url}&media_type=STORIES`
- : `video_url=${m.url}&media_type=REELS`
- : isStory
- ? `video_url=${m.url}&media_type=STORIES`
- : `video_url=${m.url}&media_type=VIDEO`
- : isStory
- ? `image_url=${m.url}&media_type=STORIES`
- : `image_url=${m.url}`;
+ const mediaType = this.getMediaType(m.url, isStory, firstPost?.media?.length > 1);
|
||
| console.log('in progress1'); | ||
|
|
||
| const collaborators = | ||
| firstPost?.settings?.collaborators?.length && !isStory | ||
| ? `&collaborators=${JSON.stringify( | ||
| firstPost?.settings?.collaborators.map((p) => p.label) | ||
| )}` | ||
| : ``; | ||
|
|
||
| console.log(collaborators); | ||
| const { id: photoId } = await ( | ||
| await this.fetch( | ||
| `https://graph.facebook.com/v20.0/${id}/media?${mediaType}${isCarousel}&access_token=${accessToken}${caption}`, | ||
| `https://graph.facebook.com/v20.0/${id}/media?${mediaType}${isCarousel}${collaborators}&access_token=${accessToken}${caption}`, | ||
| { | ||
| method: 'POST', | ||
| } | ||
| ) | ||
| ).json(); | ||
| console.log('in progress2'); | ||
|
|
||
| let status = 'IN_PROGRESS'; | ||
| while (status === 'IN_PROGRESS') { | ||
|
|
@@ -241,6 +259,7 @@ export class InstagramProvider | |
| await timer(3000); | ||
| status = status_code; | ||
| } | ||
| console.log('in progress3'); | ||
|
|
||
| return photoId; | ||
| }) || [] | ||
|
|
@@ -376,4 +395,12 @@ export class InstagramProvider | |
| })) || [] | ||
| ); | ||
| } | ||
|
|
||
| music(accessToken: string, data: { q: string }) { | ||
| return this.fetch( | ||
| `https://graph.facebook.com/v20.0/music/search?q=${encodeURIComponent( | ||
| data.q | ||
| )}&access_token=${accessToken}` | ||
| ); | ||
| } | ||
|
Comment on lines
+399
to
+405
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add error handling to the music method. The - music(accessToken: string, data: { q: string }) {
- return this.fetch(
- `https://graph.facebook.com/v20.0/music/search?q=${encodeURIComponent(
- data.q
- )}&access_token=${accessToken}`
- );
+ async music(accessToken: string, data: { q: string }): Promise<any> {
+ try {
+ const response = await this.fetch(
+ `https://graph.facebook.com/v20.0/music/search?q=${encodeURIComponent(
+ data.q
+ )}&access_token=${accessToken}`
+ );
+ return response.json();
+ } catch (error) {
+ throw new Error(`Failed to search music: ${error.message}`);
+ }
}
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider using a generic type parameter instead of
anyfor better type safety.The
settingsparameter usinganytype could lead to runtime errors. Consider using a generic type parameter to maintain type safety across different integrations.📝 Committable suggestion