diff --git a/apps/backend/src/api/routes/integrations.controller.ts b/apps/backend/src/api/routes/integrations.controller.ts index fb3eee3b..7ae2136d 100644 --- a/apps/backend/src/api/routes/integrations.controller.ts +++ b/apps/backend/src/api/routes/integrations.controller.ts @@ -3,6 +3,7 @@ import { Controller, Delete, Get, + HttpException, Param, Post, Put, @@ -261,7 +262,7 @@ export class IntegrationsController { throw new Error('Invalid integration'); } - let newList: any[] | {none: true} = []; + let newList: any[] | { none: true } = []; try { newList = (await this.functionIntegration(org, body)) || []; } catch (err) { @@ -298,7 +299,7 @@ export class IntegrationsController { image: p.image, label: p.name, })), - ...newList as any[], + ...(newList as any[]), ], (p) => p.id ).filter((f) => f.label && f.id); @@ -487,6 +488,18 @@ export class IntegrationsController { validName = `Channel_${String(id).slice(0, 8)}`; } } + + if ( + process.env.STRIPE_PUBLISHABLE_KEY && + org.isTrailing && + (await this._integrationService.checkPreviousConnections( + org.id, + String(id) + )) + ) { + throw new HttpException('', 412); + } + return this._integrationService.createOrUpdateIntegration( additionalSettings, !!integrationProvider.oneTimeToken, diff --git a/apps/cron/src/tasks/check.missing.queues.ts b/apps/cron/src/tasks/check.missing.queues.ts index c383ab6a..6c9def7f 100644 --- a/apps/cron/src/tasks/check.missing.queues.ts +++ b/apps/cron/src/tasks/check.missing.queues.ts @@ -19,9 +19,11 @@ export class CheckMissingQueues { id: p.id, publishDate: p.publishDate, isJob: - (await this._workerServiceProducer - .getQueue('post') - .getJobState(p.id)) === 'delayed', + ['delayed', 'waiting'].indexOf( + await this._workerServiceProducer + .getQueue('post') + .getJobState(p.id) + ) > -1, })) ) ).filter((p) => !p.isJob); diff --git a/apps/cron/src/tasks/post.now.pending.queues.ts b/apps/cron/src/tasks/post.now.pending.queues.ts index 3138a25c..69105304 100644 --- a/apps/cron/src/tasks/post.now.pending.queues.ts +++ b/apps/cron/src/tasks/post.now.pending.queues.ts @@ -18,9 +18,11 @@ export class PostNowPendingQueues { id: p.id, publishDate: p.publishDate, isJob: - (await this._workerServiceProducer - .getQueue('post') - .getJobState(p.id)) === 'delayed', + ['delayed', 'waiting'].indexOf( + await this._workerServiceProducer + .getQueue('post') + .getJobState(p.id) + ) > -1, })) ) ).filter((p) => !p.isJob); diff --git a/apps/frontend/src/app/(app)/layout.tsx b/apps/frontend/src/app/(app)/layout.tsx index e30d05f2..bea3e05e 100644 --- a/apps/frontend/src/app/(app)/layout.tsx +++ b/apps/frontend/src/app/(app)/layout.tsx @@ -18,13 +18,13 @@ import { FacebookComponent } from '@gitroom/frontend/components/layout/facebook. import { headers } from 'next/headers'; import { headerName } from '@gitroom/react/translation/i18n.config'; import { HtmlComponent } from '@gitroom/frontend/components/layout/html.component'; -import dynamicLoad from 'next/dynamic'; -const SetTimezone = dynamicLoad( - () => import('@gitroom/frontend/components/layout/set.timezone'), - { - ssr: false, - } -); +// import dynamicLoad from 'next/dynamic'; +// const SetTimezone = dynamicLoad( +// () => import('@gitroom/frontend/components/layout/set.timezone'), +// { +// ssr: false, +// } +// ); const jakartaSans = Plus_Jakarta_Sans({ weight: ['600', '500'], @@ -79,7 +79,7 @@ export default async function AppLayout({ children }: { children: ReactNode }) { } > - + {/**/} diff --git a/apps/frontend/src/components/billing/main.billing.component.tsx b/apps/frontend/src/components/billing/main.billing.component.tsx index 00476764..cad4e3bc 100644 --- a/apps/frontend/src/components/billing/main.billing.component.tsx +++ b/apps/frontend/src/components/billing/main.billing.component.tsx @@ -505,7 +505,7 @@ export const MainBillingComponent: FC<{ {t( 'your_subscription_will_be_canceled_at', 'Your subscription will be canceled at' - )} + )}{' '} {newDayjs(subscription.cancelAt).local().format('D MMM, YYYY')}
{t( diff --git a/apps/frontend/src/components/launches/add.provider.component.tsx b/apps/frontend/src/components/launches/add.provider.component.tsx index a72c4b1a..bf83b7ed 100644 --- a/apps/frontend/src/components/launches/add.provider.component.tsx +++ b/apps/frontend/src/components/launches/add.provider.component.tsx @@ -1,14 +1,14 @@ 'use client'; import { useModals } from '@mantine/modals'; -import React, { FC, useCallback, useMemo } from 'react'; +import React, { FC, useCallback, useEffect, useMemo } from 'react'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; import { Input } from '@gitroom/react/form/input'; import { FieldValues, FormProvider, useForm } from 'react-hook-form'; import { Button } from '@gitroom/react/form/button'; import { classValidatorResolver } from '@hookform/resolvers/class-validator'; import { ApiKeyDto } from '@gitroom/nestjs-libraries/dtos/integrations/api.key.dto'; -import { useRouter } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component'; import { useVariables } from '@gitroom/react/helpers/variable.context'; import { useToaster } from '@gitroom/react/toaster/toaster'; @@ -42,9 +42,16 @@ export const AddProviderButton: FC<{ update?: () => void; }> = (props) => { const { update } = props; + const query = useSearchParams(); const add = useAddProvider(update); const t = useT(); + useEffect(() => { + if (query.get('onboarding')) { + add(); + } + }, []); + return ( + + + + ); +}; +export const PreConditionComponent: FC = () => { + const modal = useModals(); + const query = useSearchParams(); + useEffect(() => { + if (query.get('precondition')) { + modal.openModal({ + title: '', + withCloseButton: false, + classNames: { + modal: 'text-textColor', + }, + size: 'auto', + children: ( + + + + ), + }); + } + }, []); + return null; +}; diff --git a/apps/frontend/src/components/layout/set.timezone.tsx b/apps/frontend/src/components/layout/set.timezone.tsx index 1835b96e..f37b4f0f 100644 --- a/apps/frontend/src/components/layout/set.timezone.tsx +++ b/apps/frontend/src/components/layout/set.timezone.tsx @@ -16,7 +16,7 @@ export const getTimezone = () => { }; export const newDayjs = (config?: ConfigType) => { - return dayjs.tz(config, getTimezone()); + return dayjs(config); }; const SetTimezone: FC = () => { diff --git a/apps/frontend/src/components/new-launch/editor.tsx b/apps/frontend/src/components/new-launch/editor.tsx index 681589fc..6d6c9b09 100644 --- a/apps/frontend/src/components/new-launch/editor.tsx +++ b/apps/frontend/src/components/new-launch/editor.tsx @@ -759,66 +759,84 @@ export const OnlyEditor = forwardRef< InterceptUnderlineShortcut, BulletList, ListItem, - Link.configure({ - openOnClick: false, - autolink: true, - defaultProtocol: 'https', - protocols: ['http', 'https'], - isAllowedUri: (url, ctx) => { - try { - // construct URL - const parsedUrl = url.includes(':') - ? new URL(url) - : new URL(`${ctx.defaultProtocol}://${url}`); + ...(editorType === 'html' || editorType === 'markdown' + ? [ + Link.configure({ + openOnClick: false, + autolink: true, + defaultProtocol: 'https', + protocols: ['http', 'https'], + isAllowedUri: (url, ctx) => { + try { + // prevent transforming plain emails like foo@bar.com into links + const trimmed = String(url).trim(); + const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (emailPattern.test(trimmed)) { + return false; + } - // use default validation - if (!ctx.defaultValidate(parsedUrl.href)) { - return false; - } + // construct URL + const parsedUrl = url.includes(':') + ? new URL(url) + : new URL(`${ctx.defaultProtocol}://${url}`); - // disallowed protocols - const disallowedProtocols = ['ftp', 'file', 'mailto']; - const protocol = parsedUrl.protocol.replace(':', ''); + // use default validation + if (!ctx.defaultValidate(parsedUrl.href)) { + return false; + } - if (disallowedProtocols.includes(protocol)) { - return false; - } + // disallowed protocols + const disallowedProtocols = ['ftp', 'file', 'mailto']; + const protocol = parsedUrl.protocol.replace(':', ''); - // only allow protocols specified in ctx.protocols - const allowedProtocols = ctx.protocols.map((p) => - typeof p === 'string' ? p : p.scheme - ); + if (disallowedProtocols.includes(protocol)) { + return false; + } - if (!allowedProtocols.includes(protocol)) { - return false; - } + // only allow protocols specified in ctx.protocols + const allowedProtocols = ctx.protocols.map((p) => + typeof p === 'string' ? p : p.scheme + ); - // all checks have passed - return true; - } catch { - return false; - } - }, - shouldAutoLink: (url) => { - try { - // construct URL - const parsedUrl = url.includes(':') - ? new URL(url) - : new URL(`https://${url}`); + if (!allowedProtocols.includes(protocol)) { + return false; + } - // only auto-link if the domain is not in the disallowed list - const disallowedDomains = [ - 'example-no-autolink.com', - 'another-no-autolink.com', - ]; - const domain = parsedUrl.hostname; + // all checks have passed + return true; + } catch { + return false; + } + }, + shouldAutoLink: (url) => { + try { + // prevent auto-linking of plain emails like foo@bar.com + const trimmed = String(url).trim(); + const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (emailPattern.test(trimmed)) { + return false; + } - return !disallowedDomains.includes(domain); - } catch { - return false; - } - }, - }), + // construct URL + const parsedUrl = url.includes(':') + ? new URL(url) + : new URL(`https://${url}`); + + // only auto-link if the domain is not in the disallowed list + const disallowedDomains = [ + 'example-no-autolink.com', + 'another-no-autolink.com', + ]; + const domain = parsedUrl.hostname; + + return !disallowedDomains.includes(domain); + } catch { + return false; + } + }, + }), + ] + : []), ...(internal?.integration?.id ? [ Mention.configure({ @@ -839,9 +857,13 @@ export const OnlyEditor = forwardRef< }), ] : []), - Heading.configure({ - levels: [1, 2, 3], - }), + ...(editorType === 'html' || editorType === 'markdown' + ? [ + Heading.configure({ + levels: [1, 2, 3], + }), + ] + : []), History.configure({ depth: 100, // default is 100 newGroupDelay: 100, // default is 500ms diff --git a/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx b/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx index 1a02aa41..eb888169 100644 --- a/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/high.order.provider.tsx @@ -54,6 +54,7 @@ export const withProvider = function (params: { value: Array< Array<{ path: string; + thumbnail?: string; }> >, settings: T, diff --git a/apps/frontend/src/components/new-launch/providers/reddit/reddit.provider.tsx b/apps/frontend/src/components/new-launch/providers/reddit/reddit.provider.tsx index 68e4aefe..18ce5cae 100644 --- a/apps/frontend/src/components/new-launch/providers/reddit/reddit.provider.tsx +++ b/apps/frontend/src/components/new-launch/providers/reddit/reddit.provider.tsx @@ -111,7 +111,6 @@ const RedditPreview: FC = (props) => {
{value.title}
-
{ + if ( + settings?.subreddit?.some( + (p: any, index: number) => + p?.value?.type === 'media' && posts[0].length !== 1 + ) + ) { + return 'When posting a media post, you must attached exactly one media file.'; + } + + if ( + posts.some((p) => + p.some((a) => !a.thumbnail && a.path.indexOf('mp4') > -1) + ) + ) { + return 'You must attach a thumbnail to your video post.'; + } + + return true; + }, maximumCharacters: 10000, }); diff --git a/apps/frontend/src/components/new-launch/providers/reddit/subreddit.tsx b/apps/frontend/src/components/new-launch/providers/reddit/subreddit.tsx index f53db361..9e002e1f 100644 --- a/apps/frontend/src/components/new-launch/providers/reddit/subreddit.tsx +++ b/apps/frontend/src/components/new-launch/providers/reddit/subreddit.tsx @@ -233,24 +233,6 @@ export const Subreddit: FC<{ onChange={setURL} /> )} - {value.type === 'media' && ( -
-
-
- -
-
- )} ) : (
diff --git a/apps/frontend/src/components/new-launch/store.ts b/apps/frontend/src/components/new-launch/store.ts index d92d169e..754c833d 100644 --- a/apps/frontend/src/components/new-launch/store.ts +++ b/apps/frontend/src/components/new-launch/store.ts @@ -11,7 +11,7 @@ import { newDayjs } from '@gitroom/frontend/components/layout/set.timezone'; interface Values { id: string; content: string; - media: { id: string; path: string }[]; + media: { id: string; path: string, thumbnail?: string }[]; } interface Internal { diff --git a/apps/frontend/src/components/new-layout/layout.component.tsx b/apps/frontend/src/components/new-layout/layout.component.tsx index 14063dbb..abcfe4c5 100644 --- a/apps/frontend/src/components/new-layout/layout.component.tsx +++ b/apps/frontend/src/components/new-layout/layout.component.tsx @@ -38,6 +38,7 @@ import { ChromeExtensionComponent } from '@gitroom/frontend/components/layout/ch import NotificationComponent from '@gitroom/frontend/components/notifications/notification.component'; import { BillingAfter } from '@gitroom/frontend/components/new-layout/billing.after'; import { OrganizationSelector } from '@gitroom/frontend/components/layout/organization.selector'; +import { PreConditionComponent } from '@gitroom/frontend/components/layout/pre-condition.component'; const jakartaSans = Plus_Jakarta_Sans({ weight: ['600', '500'], @@ -79,8 +80,8 @@ export const LayoutComponent = ({ children }: { children: ReactNode }) => { + - {user.tier !== 'FREE' && }
{ ))} -
Current Timezone
- + {/*
Current Timezone
*/} + {/**/} + {/* {timezones.map((metric) => (*/} + {/* */} + {/* {metric.label}*/} + {/* */} + {/* ))}*/} + {/**/}
); }; diff --git a/libraries/helpers/src/utils/concurrency.service.ts b/libraries/helpers/src/utils/concurrency.service.ts index 7d447e87..4f241493 100644 --- a/libraries/helpers/src/utils/concurrency.service.ts +++ b/libraries/helpers/src/utils/concurrency.service.ts @@ -1,6 +1,7 @@ import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service'; import Bottleneck from 'bottleneck'; import { timer } from '@gitroom/helpers/utils/timer'; +import { BadBody } from '@gitroom/nestjs-libraries/integrations/social.abstract'; const connection = new Bottleneck.IORedisConnection({ client: ioRedis, @@ -11,7 +12,8 @@ const mapper = {} as Record; export const concurrency = async ( identifier: string, maxConcurrent = 1, - func: (...args: any[]) => Promise + func: (...args: any[]) => Promise, + ignoreConcurrency = false ) => { const strippedIdentifier = identifier.toLowerCase().split('-')[0]; mapper[strippedIdentifier] ??= new Bottleneck({ @@ -22,16 +24,29 @@ export const concurrency = async ( minTime: 1000, }); let load: T; + + if (ignoreConcurrency) { + return await func(); + } + try { load = await mapper[strippedIdentifier].schedule( - { expiration: 600000 }, + { expiration: 60000 }, async () => { try { return await func(); } catch (err) {} } ); - } catch (err) {} + } catch (err) { + console.log(err); + throw new BadBody( + identifier, + JSON.stringify({}), + {} as any, + `Something is wrong with ${identifier}` + ); + } return load; }; diff --git a/libraries/helpers/src/utils/strip.html.validation.ts b/libraries/helpers/src/utils/strip.html.validation.ts index e5abdab4..7ad3591c 100644 --- a/libraries/helpers/src/utils/strip.html.validation.ts +++ b/libraries/helpers/src/utils/strip.html.validation.ts @@ -232,7 +232,7 @@ export const stripHtmlValidation = ( convertMentionFunction ); - return striptags(processedHtml, ['h1', 'h2', 'h3']); + return striptags(processedHtml); } // Strip all other tags diff --git a/libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts b/libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts index 8cb52190..d6d7efa7 100644 --- a/libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts +++ b/libraries/nestjs-libraries/src/bull-mq-transport-new/strategy.ts @@ -34,7 +34,7 @@ export class BullMqServer extends Server implements CustomTransportStrategy { }, { maxStalledCount: 10, - concurrency: 5, + concurrency: 300, connection: ioRedis, removeOnComplete: { count: 0, diff --git a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts index ae576223..6024b921 100644 --- a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts +++ b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.repository.ts @@ -55,7 +55,7 @@ export class IntegrationRepository { mentions: { name: string; username: string; image: string }[] ) { if (mentions.length === 0) { - return []; + return [] as any[]; } return this._mentions.model.mentions.createMany({ data: mentions.map((mention) => ({ @@ -68,6 +68,24 @@ export class IntegrationRepository { }); } + async checkPreviousConnections(org: string, id: string) { + const findIt = await this._integration.model.integration.findMany({ + where: { + rootInternalId: id.split('_').pop(), + }, + select: { + organizationId: true, + id: true, + }, + }); + + if (findIt.some((f) => f.organizationId === org)) { + return false; + } + + return findIt.length > 0; + } + updateProviderSettings(org: string, id: string, settings: string) { return this._integration.model.integration.update({ where: { diff --git a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts index d7519354..a54758e6 100644 --- a/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts +++ b/libraries/nestjs-libraries/src/database/prisma/integrations/integration.service.ts @@ -73,6 +73,10 @@ export class IntegrationService { ); } + checkPreviousConnections(org: string, id: string) { + return this._integrationRepository.checkPreviousConnections(org, id); + } + async createOrUpdateIntegration( additionalSettings: | { diff --git a/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts index 9508a26c..96fccaff 100644 --- a/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts +++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts @@ -48,6 +48,11 @@ export class PostsRepository { searchForMissingThreeHoursPosts() { return this._post.model.post.findMany({ where: { + integration: { + refreshNeeded: false, + inBetweenSteps: false, + disabled: false, + }, publishDate: { gte: dayjs.utc().toDate(), lt: dayjs.utc().add(3, 'hour').toDate(), @@ -66,6 +71,11 @@ export class PostsRepository { getOldPosts(orgId: string, date: string) { return this._post.model.post.findMany({ where: { + integration: { + refreshNeeded: false, + inBetweenSteps: false, + disabled: false, + }, organizationId: orgId, publishDate: { lte: dayjs(date).toDate(), diff --git a/libraries/nestjs-libraries/src/database/prisma/schema.prisma b/libraries/nestjs-libraries/src/database/prisma/schema.prisma index 6027caa2..0958133a 100644 --- a/libraries/nestjs-libraries/src/database/prisma/schema.prisma +++ b/libraries/nestjs-libraries/src/database/prisma/schema.prisma @@ -338,9 +338,14 @@ model Integration { @@unique([organizationId, internalId]) @@index([rootInternalId]) @@index([organizationId]) + @@index([providerIdentifier]) @@index([updatedAt]) + @@index([createdAt]) @@index([deletedAt]) @@index([customerId]) + @@index([inBetweenSteps]) + @@index([refreshNeeded]) + @@index([disabled]) } model Signatures { diff --git a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/reddit.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/reddit.dto.ts index 1eeef00f..8785ddbe 100644 --- a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/reddit.dto.ts +++ b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/reddit.dto.ts @@ -9,7 +9,6 @@ import { ValidateIf, ValidateNested, } from 'class-validator'; -import { MediaDto } from '@gitroom/nestjs-libraries/dtos/media/media.dto'; import { Type } from 'class-transformer'; export class RedditFlairDto { @@ -57,12 +56,6 @@ export class RedditSettingsDtoInner { @IsDefined() @ValidateNested() flair: RedditFlairDto; - - @ValidateIf((e) => e.type === 'media') - @ValidateNested({ each: true }) - @Type(() => MediaDto) - @ArrayMinSize(1) - media: MediaDto[]; } export class RedditSettingsValueDto { diff --git a/libraries/nestjs-libraries/src/emails/resend.provider.ts b/libraries/nestjs-libraries/src/emails/resend.provider.ts index b08c0eed..c0519b0f 100644 --- a/libraries/nestjs-libraries/src/emails/resend.provider.ts +++ b/libraries/nestjs-libraries/src/emails/resend.provider.ts @@ -14,14 +14,20 @@ export class ResendProvider implements EmailInterface { emailFromAddress: string, replyTo?: string ) { - const sends = await resend.emails.send({ - from: `${emailFromName} <${emailFromAddress}>`, - to, - subject, - html, - ...(replyTo && { reply_to: replyTo }), - }); + try { + const sends = await resend.emails.send({ + from: `${emailFromName} <${emailFromAddress}>`, + to, + subject, + html, + ...(replyTo && { reply_to: replyTo }), + }); - return sends; + return sends; + } catch (err) { + console.log(err); + } + + return { sent: false }; } } diff --git a/libraries/nestjs-libraries/src/integrations/social.abstract.ts b/libraries/nestjs-libraries/src/integrations/social.abstract.ts index 3c3aedfd..0b4d26b1 100644 --- a/libraries/nestjs-libraries/src/integrations/social.abstract.ts +++ b/libraries/nestjs-libraries/src/integrations/social.abstract.ts @@ -68,12 +68,14 @@ export abstract class SocialAbstract { url: string, options: RequestInit = {}, identifier = '', - totalRetries = 0 + totalRetries = 0, + ignoreConcurrency = false ): Promise { const request = await concurrency( this.identifier, this.maxConcurrentJob, - () => fetch(url, options) + () => fetch(url, options), + ignoreConcurrency ); if (request.status === 200 || request.status === 201) { diff --git a/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts b/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts index c90990fb..1803992b 100644 --- a/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/bluesky.provider.ts @@ -52,7 +52,7 @@ async function reduceImageBySize(url: string, maxSizeKB = 976) { if (width < 10 || height < 10) break; // Prevent overly small dimensions } - return imageBuffer; + return { width, height, buffer: imageBuffer }; } catch (error) { console.error('Error processing image:', error); throw error; @@ -213,7 +213,7 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider { accessToken: accessJwt, id: did, name: profile.data.displayName!, - picture: profile.data.avatar!, + picture: profile?.data?.avatar || '', username: profile.data.handle!, }; } catch (e) { @@ -259,9 +259,12 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider { // Upload images const images = await Promise.all( imageMedia.map(async (p) => { - return await agent.uploadBlob( - new Blob([await reduceImageBySize(p.path)]) - ); + const { buffer, width, height } = await reduceImageBySize(p.path); + return { + width, + height, + buffer: await agent.uploadBlob(new Blob([buffer])), + }; }) ); @@ -288,7 +291,11 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider { $type: 'app.bsky.embed.images', images: images.map((p, index) => ({ alt: imageMedia?.[index]?.alt || '', - image: p.data.blob, + image: p.buffer.data.blob, + aspectRatio: { + width: p.width, + height: p.height, + } })), }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/dev.to.provider.ts b/libraries/nestjs-libraries/src/integrations/social/dev.to.provider.ts index a8e422b4..53487b3d 100644 --- a/libraries/nestjs-libraries/src/integrations/social/dev.to.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/dev.to.provider.ts @@ -81,7 +81,7 @@ export class DevToProvider extends SocialAbstract implements SocialProvider { accessToken: body.apiKey, id, name, - picture: profile_image, + picture: profile_image || '', username, }; } catch (err) { diff --git a/libraries/nestjs-libraries/src/integrations/social/discord.provider.ts b/libraries/nestjs-libraries/src/integrations/social/discord.provider.ts index 54728649..8796147c 100644 --- a/libraries/nestjs-libraries/src/integrations/social/discord.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/discord.provider.ts @@ -242,7 +242,7 @@ export class DiscordProvider extends SocialAbstract implements SocialProvider { .filter((role: any) => role.name.toLowerCase().includes(data.query.toLowerCase()) ) - .filter((f) => f.name !== '@everyone' && f.name !== '@here'); + .filter((f: any) => f.name !== '@everyone' && f.name !== '@here'); const list = await ( await fetch( diff --git a/libraries/nestjs-libraries/src/integrations/social/dribbble.provider.ts b/libraries/nestjs-libraries/src/integrations/social/dribbble.provider.ts index a8b76758..4e231085 100644 --- a/libraries/nestjs-libraries/src/integrations/social/dribbble.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/dribbble.provider.ts @@ -54,7 +54,7 @@ export class DribbbleProvider extends SocialAbstract implements SocialProvider { accessToken: access_token, refreshToken: refreshToken, expiresIn: expires_in, - picture: profile_image, + picture: profile_image || '', username, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts b/libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts index be62e6d3..c788d5b6 100644 --- a/libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/facebook.provider.ts @@ -236,9 +236,7 @@ export class FacebookProvider extends SocialAbstract implements SocialProvider { const { id, name, - picture: { - data: { url }, - }, + picture } = await ( await fetch( `https://graph.facebook.com/v20.0/me?fields=id,name,picture&access_token=${access_token}` @@ -251,7 +249,7 @@ export class FacebookProvider extends SocialAbstract implements SocialProvider { accessToken: access_token, refreshToken: access_token, expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(), - picture: url, + picture: picture?.data?.url || '', username: '', }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/farcaster.provider.ts b/libraries/nestjs-libraries/src/integrations/social/farcaster.provider.ts index 4bb81bdd..212b4010 100644 --- a/libraries/nestjs-libraries/src/integrations/social/farcaster.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/farcaster.provider.ts @@ -61,7 +61,7 @@ export class FarcasterProvider accessToken: data.signer_uuid, refreshToken: '', expiresIn: dayjs().add(200, 'year').unix() - dayjs().unix(), - picture: data.pfp_url, + picture: data?.pfp_url || '', username: data.username, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/hashnode.provider.ts b/libraries/nestjs-libraries/src/integrations/social/hashnode.provider.ts index eee8978e..8fdd768e 100644 --- a/libraries/nestjs-libraries/src/integrations/social/hashnode.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/hashnode.provider.ts @@ -91,7 +91,7 @@ export class HashnodeProvider extends SocialAbstract implements SocialProvider { accessToken: body.apiKey, id, name, - picture: profilePicture, + picture: profilePicture || '', username, }; } catch (err) { diff --git a/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts b/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts index 3927c3d1..2bdf2889 100644 --- a/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/instagram.provider.ts @@ -364,9 +364,7 @@ export class InstagramProvider const { id, name, - picture: { - data: { url }, - }, + picture } = await ( await fetch( `https://graph.facebook.com/v20.0/me?fields=id,name,picture&access_token=${access_token}` @@ -379,7 +377,7 @@ export class InstagramProvider accessToken: access_token, refreshToken: access_token, expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(), - picture: url, + picture: picture?.data?.url || '', username: '', }; } @@ -498,10 +496,14 @@ export class InstagramProvider while (status === 'IN_PROGRESS') { const { status_code } = await ( await this.fetch( - `https://${type}/v20.0/${photoId}?access_token=${accessToken}&fields=status_code` + `https://${type}/v20.0/${photoId}?access_token=${accessToken}&fields=status_code`, + undefined, + '', + 0, + true, ) ).json(); - await timer(10000); + await timer(30000); status = status_code; } console.log('in progress3', id); @@ -558,10 +560,14 @@ export class InstagramProvider while (status === 'IN_PROGRESS') { const { status_code } = await ( await this.fetch( - `https://${type}/v20.0/${containerId}?fields=status_code&access_token=${accessToken}` + `https://${type}/v20.0/${containerId}?fields=status_code&access_token=${accessToken}`, + undefined, + '', + 0, + true ) ).json(); - await timer(10000); + await timer(30000); status = status_code; } diff --git a/libraries/nestjs-libraries/src/integrations/social/instagram.standalone.provider.ts b/libraries/nestjs-libraries/src/integrations/social/instagram.standalone.provider.ts index a74ab431..68938b17 100644 --- a/libraries/nestjs-libraries/src/integrations/social/instagram.standalone.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/instagram.standalone.provider.ts @@ -41,7 +41,7 @@ export class InstagramStandaloneProvider ) ).json(); - const { user_id, name, username, profile_picture_url } = await ( + const { user_id, name, username, profile_picture_url = '' } = await ( await fetch( `https://graph.instagram.com/v21.0/me?fields=user_id,username,name,profile_picture_url&access_token=${access_token}` ) @@ -53,7 +53,7 @@ export class InstagramStandaloneProvider accessToken: access_token, refreshToken: access_token, expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(), - picture: profile_picture_url, + picture: profile_picture_url || '', username, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/lemmy.provider.ts b/libraries/nestjs-libraries/src/integrations/social/lemmy.provider.ts index 5f2e0e6d..6b5a9759 100644 --- a/libraries/nestjs-libraries/src/integrations/social/lemmy.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/lemmy.provider.ts @@ -107,7 +107,7 @@ export class LemmyProvider extends SocialAbstract implements SocialProvider { user.person_view.person.display_name || user.person_view.person.name || '', - picture: user.person_view.person.avatar || '', + picture: user?.person_view?.person?.avatar || '', username: body.identifier || '', }; } catch (e) { diff --git a/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts b/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts index aac124f7..d4731735 100644 --- a/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/linkedin.provider.ts @@ -80,7 +80,7 @@ export class LinkedinProvider extends SocialAbstract implements SocialProvider { refreshToken, expiresIn: expires_in, name, - picture, + picture: picture || '', username: vanityName, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/mastodon.provider.ts b/libraries/nestjs-libraries/src/integrations/social/mastodon.provider.ts index 413f637c..72957765 100644 --- a/libraries/nestjs-libraries/src/integrations/social/mastodon.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/mastodon.provider.ts @@ -91,7 +91,7 @@ export class MastodonProvider extends SocialAbstract implements SocialProvider { accessToken: tokenInformation.access_token, refreshToken: 'null', expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(), - picture: personalInformation.avatar, + picture: personalInformation?.avatar || '', username: personalInformation.username, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/medium.provider.ts b/libraries/nestjs-libraries/src/integrations/social/medium.provider.ts index db838978..00b91c01 100644 --- a/libraries/nestjs-libraries/src/integrations/social/medium.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/medium.provider.ts @@ -72,7 +72,7 @@ export class MediumProvider extends SocialAbstract implements SocialProvider { accessToken: body.apiKey, id, name, - picture: imageUrl, + picture: imageUrl || '', username, }; } catch (err) { diff --git a/libraries/nestjs-libraries/src/integrations/social/nostr.provider.ts b/libraries/nestjs-libraries/src/integrations/social/nostr.provider.ts index ccfd2871..ec961d60 100644 --- a/libraries/nestjs-libraries/src/integrations/social/nostr.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/nostr.provider.ts @@ -147,7 +147,7 @@ export class NostrProvider extends SocialAbstract implements SocialProvider { accessToken: AuthService.signJWT({ password: body.password }), refreshToken: '', expiresIn: dayjs().add(200, 'year').unix() - dayjs().unix(), - picture: user.picture, + picture: user?.picture || '', username: user.name || 'nousername', }; } catch (e) { diff --git a/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts b/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts index 8ee14294..585d188c 100644 --- a/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/pinterest.provider.ts @@ -37,7 +37,6 @@ export class PinterestProvider value: string; } | undefined { - if (body.indexOf('cover_image_url or cover_image_content_type') > -1) { return { type: 'bad-body' as const, @@ -83,7 +82,7 @@ export class PinterestProvider accessToken: access_token, refreshToken: refreshToken, expiresIn: expires_in, - picture: profile_image, + picture: profile_image || '', username, }; } @@ -212,12 +211,18 @@ export class PinterestProvider 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}`, + await this.fetch( + 'https://api.pinterest.com/v5/media/' + media_id, + { + method: 'GET', + headers: { + Authorization: `Bearer ${accessToken}`, + }, }, - }) + '', + 0, + true + ) ).json(); await timer(30000); diff --git a/libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts b/libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts index 2676706a..f249c023 100644 --- a/libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/reddit.provider.ts @@ -9,6 +9,12 @@ import { RedditSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/provider import { timer } from '@gitroom/helpers/utils/timer'; import { groupBy } from 'lodash'; import { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract'; +import { lookup } from 'mime-types'; +import axios from 'axios'; +import WebSocket from 'ws'; + +// @ts-ignore +global.WebSocket = WebSocket; export class RedditProvider extends SocialAbstract implements SocialProvider { override maxConcurrentJob = 1; // Reddit has strict rate limits (1 request per second) @@ -53,7 +59,7 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { accessToken, refreshToken: newRefreshToken, expiresIn, - picture: icon_img.split('?')[0], + picture: icon_img?.split?.('?')?.[0] || '', username: name, }; } @@ -112,11 +118,60 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { accessToken, refreshToken, expiresIn, - picture: icon_img.split('?')[0], + picture: icon_img?.split?.('?')?.[0] || '', username: name, }; } + private async uploadFileToReddit(accessToken: string, path: string) { + const mimeType = lookup(path); + const formData = new FormData(); + formData.append('filepath', path.split('/').pop()); + formData.append('mimetype', mimeType || 'application/octet-stream'); + + const { + args: { action, fields }, + } = await ( + await this.fetch( + 'https://oauth.reddit.com/api/media/asset', + { + method: 'POST', + headers: { + Authorization: `Bearer ${accessToken}`, + }, + body: formData, + }, + 'reddit', + 0, + true + ) + ).json(); + + const { data } = await axios.get(path, { + responseType: 'arraybuffer', + }); + + const upload = (fields as { name: string; value: string }[]).reduce( + (acc, value) => { + acc.append(value.name, value.value); + return acc; + }, + new FormData() + ); + + upload.append( + 'file', + new Blob([Buffer.from(data)], { type: mimeType as string }) + ); + + const d = await fetch('https:' + action, { + method: 'POST', + body: upload, + }); + + return [...(await d.text()).matchAll(/(.*?)<\/Location>/g)][0][1]; + } + async post( id: string, accessToken: string, @@ -131,7 +186,9 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { title: firstPostSettings.value.title || '', kind: firstPostSettings.value.type === 'media' - ? 'image' + ? post.media[0].path.indexOf('mp4') > -1 + ? 'video' + : 'image' : firstPostSettings.value.type, ...(firstPostSettings.value.flair ? { flair_id: firstPostSettings.value.flair.id } @@ -143,22 +200,25 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { : {}), ...(firstPostSettings.value.type === 'media' ? { - url: `${ - firstPostSettings.value.media[0].path.indexOf('http') === -1 - ? `${process.env.NEXT_PUBLIC_BACKEND_URL}/uploads` - : `` - }${firstPostSettings.value.media[0].path}`, + url: await this.uploadFileToReddit( + accessToken, + post.media[0].path + ), + ...(post.media[0].path.indexOf('mp4') > -1 + ? { + video_poster_url: await this.uploadFileToReddit( + accessToken, + post.media[0].thumbnail + ), + } + : {}), } : {}), text: post.message, sr: firstPostSettings.value.subreddit, }; - const { - json: { - data: { id, name, url }, - }, - } = await ( + const all = await ( await this.fetch('https://oauth.reddit.com/api/submit', { method: 'POST', headers: { @@ -169,6 +229,38 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { }) ).json(); + const { id, name, url } = await new Promise<{ + id: string; + name: string; + url: string; + }>((res) => { + if (all?.json?.data?.id) { + res(all.json.data); + } + + const ws = new WebSocket(all.json.data.websocket_url); + ws.on('message', (data: any) => { + setTimeout(() => { + res({ id: '', name: '', url: '' }); + ws.close(); + }, 30_000); + try { + const parsedData = JSON.parse(data.toString()); + if (parsedData?.payload?.redirect) { + const onlyId = parsedData?.payload?.redirect.replace( + /https:\/\/www\.reddit\.com\/r\/.*?\/comments\/(.*?)\/.*/g, + '$1' + ); + res({ + id: onlyId, + name: `t3_${onlyId}`, + url: parsedData?.payload?.redirect, + }); + } + } catch (err) {} + }); + }); + valueArray.push({ postId: id, releaseURL: url, @@ -202,8 +294,6 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { }) ).json(); - // console.log(JSON.stringify(allTop, null, 2), JSON.stringify(allJson, null, 2), JSON.stringify(allData, null, 2)); - valueArray.push({ postId: commentId, releaseURL: 'https://www.reddit.com' + permalink, @@ -233,7 +323,7 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { const { data: { children }, } = await ( - await fetch( + await this.fetch( `https://oauth.reddit.com/subreddits/search?show=public&q=${data.word}&sort=activity&show_users=false&limit=10`, { method: 'GET', @@ -241,7 +331,10 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/x-www-form-urlencoded', }, - } + }, + 'reddit', + 0, + false ) ).json(); @@ -267,28 +360,34 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { permissions.push('link'); } - // if (submissionType === 'any' || allow_images) { - // permissions.push('media'); - // } + if (allow_images) { + permissions.push('media'); + } return permissions; } async restrictions(accessToken: string, data: { subreddit: string }) { const { - data: { submission_type, allow_images }, + data: { submission_type, allow_images, ...all2 }, } = await ( - await fetch(`https://oauth.reddit.com/${data.subreddit}/about`, { - method: 'GET', - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/x-www-form-urlencoded', + await this.fetch( + `https://oauth.reddit.com/${data.subreddit}/about`, + { + method: 'GET', + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/x-www-form-urlencoded', + }, }, - }) + 'reddit', + 0, + false + ) ).json(); const { is_flair_required, ...all } = await ( - await fetch( + await this.fetch( `https://oauth.reddit.com/api/v1/${ data.subreddit.split('/r/')[1] }/post_requirements`, @@ -298,7 +397,10 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/x-www-form-urlencoded', }, - } + }, + 'reddit', + 0, + false ) ).json(); @@ -307,7 +409,7 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { async (res) => { try { const flair = await ( - await fetch( + await this.fetch( `https://oauth.reddit.com/${data.subreddit}/api/link_flair_v2`, { method: 'GET', @@ -315,7 +417,10 @@ export class RedditProvider extends SocialAbstract implements SocialProvider { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/x-www-form-urlencoded', }, - } + }, + 'reddit', + 0, + false ) ).json(); diff --git a/libraries/nestjs-libraries/src/integrations/social/slack.provider.ts b/libraries/nestjs-libraries/src/integrations/social/slack.provider.ts index 459acff2..762c2bba 100644 --- a/libraries/nestjs-libraries/src/integrations/social/slack.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/slack.provider.ts @@ -95,7 +95,7 @@ export class SlackProvider extends SocialAbstract implements SocialProvider { accessToken: access_token, refreshToken: 'null', expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(), - picture: user.profile.image_original, + picture: user?.profile?.image_original || '', username: user.name, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts b/libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts index 0fc8e8c7..ad521f89 100644 --- a/libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/telegram.provider.ts @@ -71,7 +71,7 @@ export class TelegramProvider extends SocialAbstract implements SocialProvider { accessToken: String(chat.id), refreshToken: '', expiresIn: dayjs().add(200, 'year').unix() - dayjs().unix(), - picture: photo, + picture: photo || '', username: chat.username!, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/threads.provider.ts b/libraries/nestjs-libraries/src/integrations/social/threads.provider.ts index 932466b1..b4a9a2f0 100644 --- a/libraries/nestjs-libraries/src/integrations/social/threads.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/threads.provider.ts @@ -13,7 +13,6 @@ import { capitalize, chunk } from 'lodash'; import { Plug } from '@gitroom/helpers/decorators/plug.decorator'; import { Integration } from '@prisma/client'; import { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation'; -import { TwitterApi } from 'twitter-api-v2'; export class ThreadsProvider extends SocialAbstract implements SocialProvider { identifier = 'threads'; @@ -41,9 +40,7 @@ export class ThreadsProvider extends SocialAbstract implements SocialProvider { id, name, username, - picture: { - data: { url }, - }, + picture } = await this.fetchPageInformation(access_token); return { @@ -52,7 +49,7 @@ export class ThreadsProvider extends SocialAbstract implements SocialProvider { accessToken: access_token, refreshToken: access_token, expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(), - picture: url, + picture: picture?.data?.url || '', username: '', }; } @@ -112,9 +109,7 @@ export class ThreadsProvider extends SocialAbstract implements SocialProvider { id, name, username, - picture: { - data: { url }, - }, + picture, } = await this.fetchPageInformation(access_token); return { @@ -123,7 +118,7 @@ export class ThreadsProvider extends SocialAbstract implements SocialProvider { accessToken: access_token, refreshToken: access_token, expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(), - picture: url, + picture: picture?.data?.url || '', username: username, }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts index fe0700f0..85b6418e 100644 --- a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts @@ -250,7 +250,7 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider { accessToken: access_token, id: open_id.replace(/-/g, ''), name: display_name, - picture: avatar_url, + picture: avatar_url || '', username: username, }; } @@ -375,7 +375,10 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider { body: JSON.stringify({ publish_id: publishId, }), - } + }, + '', + 0, + true ) ).json(); @@ -399,11 +402,11 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider { 'titok-error-upload', JSON.stringify(post), Buffer.from(JSON.stringify(post)), - handleError?.value || '', + handleError?.value || '' ); } - await timer(3000); + await timer(10000); } } @@ -496,7 +499,11 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider { photo_cover_index: 0, photo_images: firstPost.media?.map((p) => p.path), }, - post_mode: firstPost?.settings?.content_posting_method === 'DIRECT_POST' ? 'DIRECT_POST' : 'MEDIA_UPLOAD', + post_mode: + firstPost?.settings?.content_posting_method === + 'DIRECT_POST' + ? 'DIRECT_POST' + : 'MEDIA_UPLOAD', media_type: 'PHOTO', }), }), diff --git a/libraries/nestjs-libraries/src/integrations/social/vk.provider.ts b/libraries/nestjs-libraries/src/integrations/social/vk.provider.ts index 12811204..670dd803 100644 --- a/libraries/nestjs-libraries/src/integrations/social/vk.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/vk.provider.ts @@ -65,7 +65,7 @@ export class VkProvider extends SocialAbstract implements SocialProvider { accessToken: access_token, refreshToken: refresh_token + '&&&&' + device_id, expiresIn: dayjs().add(expires_in, 'seconds').unix() - dayjs().unix(), - picture: avatar, + picture: avatar || '', username: first_name.toLowerCase(), }; } @@ -150,7 +150,7 @@ export class VkProvider extends SocialAbstract implements SocialProvider { accessToken: access_token, refreshToken: refresh_token + '&&&&' + device_id, expiresIn: dayjs().add(expires_in, 'seconds').unix() - dayjs().unix(), - picture: avatar, + picture: avatar || '', username: first_name.toLowerCase(), }; } diff --git a/libraries/nestjs-libraries/src/integrations/social/x.provider.ts b/libraries/nestjs-libraries/src/integrations/social/x.provider.ts index c73431e2..ca32f23c 100644 --- a/libraries/nestjs-libraries/src/integrations/social/x.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/x.provider.ts @@ -258,7 +258,7 @@ export class XProvider extends SocialAbstract implements SocialProvider { name, refreshToken: '', expiresIn: 999999999, - picture: profile_image_url, + picture: profile_image_url || '', username, additionalSettings: [ { diff --git a/libraries/nestjs-libraries/src/integrations/social/youtube.provider.ts b/libraries/nestjs-libraries/src/integrations/social/youtube.provider.ts index a9f0c369..7b6e6fcf 100644 --- a/libraries/nestjs-libraries/src/integrations/social/youtube.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/youtube.provider.ts @@ -132,7 +132,7 @@ export class YoutubeProvider extends SocialAbstract implements SocialProvider { refreshToken: credentials.refresh_token!, id: data.id!, name: data.name!, - picture: data.picture!, + picture: data?.picture || '', username: '', }; } @@ -178,7 +178,7 @@ export class YoutubeProvider extends SocialAbstract implements SocialProvider { refreshToken: tokens.refresh_token!, id: data.id!, name: data.name!, - picture: data.picture!, + picture: data?.picture || '', username: '', }; } diff --git a/libraries/nestjs-libraries/src/short-linking/short.link.service.ts b/libraries/nestjs-libraries/src/short-linking/short.link.service.ts index 3301606c..b3706713 100644 --- a/libraries/nestjs-libraries/src/short-linking/short.link.service.ts +++ b/libraries/nestjs-libraries/src/short-linking/short.link.service.ts @@ -37,7 +37,8 @@ export class ShortLinkService { } const mergeMessages = messages.join(' '); - const urlRegex = /(https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*))/gm; + const urlRegex = + /(https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*))/gm; const urls = mergeMessages.match(urlRegex); if (!urls) { // No URLs found, return the original text @@ -49,12 +50,20 @@ export class ShortLinkService { ); } - async convertTextToShortLinks(id: string, messages: string[]) { + async convertTextToShortLinks(id: string, messagesList: string[]) { if (ShortLinkService.provider.shortLinkDomain === 'empty') { - return messages; + return messagesList; } - const urlRegex = /(https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*))/gm; + const messages = messagesList.map((text) => { + return text + .replace(/&/g, '&') + .replace(/?/g, '?') + .replace(/#/g, '#'); + }); + + const urlRegex = + /(https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*))/gm; return Promise.all( messages.map(async (text) => { const urls = uniq(text.match(urlRegex));