From 0abd68961f7cea8a652fba84a3a19afafdd8dcc0 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Fri, 18 Apr 2025 19:45:45 +0700 Subject: [PATCH] feat: vk provider --- apps/frontend/public/icons/platforms/vk.png | Bin 0 -> 14610 bytes .../social/[provider]/continue/page.tsx | 8 + .../launches/providers/show.all.providers.tsx | 2 + .../launches/providers/vk/vk.provider.tsx | 11 + .../src/integrations/integration.manager.ts | 2 + .../src/integrations/social/vk.provider.ts | 246 ++++++++++++++++++ 6 files changed, 269 insertions(+) create mode 100644 apps/frontend/public/icons/platforms/vk.png create mode 100644 apps/frontend/src/components/launches/providers/vk/vk.provider.tsx create mode 100644 libraries/nestjs-libraries/src/integrations/social/vk.provider.ts diff --git a/apps/frontend/public/icons/platforms/vk.png b/apps/frontend/public/icons/platforms/vk.png new file mode 100644 index 0000000000000000000000000000000000000000..67ab342cc3830f783741c20e5991770c761647d9 GIT binary patch literal 14610 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 Yt#jI9&1Qf=2|Z?8n%bFMH}XmRUv}nGc>n+a literal 0 HcmV?d00001 diff --git a/apps/frontend/src/app/(site)/integrations/social/[provider]/continue/page.tsx b/apps/frontend/src/app/(site)/integrations/social/[provider]/continue/page.tsx index 5beb10e0..80725c24 100644 --- a/apps/frontend/src/app/(site)/integrations/social/[provider]/continue/page.tsx +++ b/apps/frontend/src/app/(site)/integrations/social/[provider]/continue/page.tsx @@ -22,6 +22,14 @@ export default async function Page({ }; } + if (provider === 'vk') { + searchParams = { + ...searchParams, + state: searchParams.state || '', + code: searchParams.code + '&&&&' + searchParams.device_id + }; + } + const data = await internalFetch(`/integrations/social/${provider}/connect`, { method: 'POST', body: JSON.stringify(searchParams), 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 0cb1e1a0..350a1acb 100644 --- a/apps/frontend/src/components/launches/providers/show.all.providers.tsx +++ b/apps/frontend/src/components/launches/providers/show.all.providers.tsx @@ -21,6 +21,7 @@ import LemmyProvider from '@gitroom/frontend/components/launches/providers/lemmy import WarpcastProvider from '@gitroom/frontend/components/launches/providers/warpcast/warpcast.provider'; import TelegramProvider from '@gitroom/frontend/components/launches/providers/telegram/telegram.provider'; import NostrProvider from '@gitroom/frontend/components/launches/providers/nostr/nostr.provider'; +import VkProvider from '@gitroom/frontend/components/launches/providers/vk/vk.provider'; export const Providers = [ {identifier: 'devto', component: DevtoProvider}, @@ -46,6 +47,7 @@ export const Providers = [ {identifier: 'wrapcast', component: WarpcastProvider}, {identifier: 'telegram', component: TelegramProvider}, {identifier: 'nostr', component: NostrProvider}, + {identifier: 'vk', component: VkProvider}, ]; diff --git a/apps/frontend/src/components/launches/providers/vk/vk.provider.tsx b/apps/frontend/src/components/launches/providers/vk/vk.provider.tsx new file mode 100644 index 00000000..d305e618 --- /dev/null +++ b/apps/frontend/src/components/launches/providers/vk/vk.provider.tsx @@ -0,0 +1,11 @@ +import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; + +export default withProvider( + null, + undefined, + undefined, + async (posts) => { + return true; + }, + 2048 +); diff --git a/libraries/nestjs-libraries/src/integrations/integration.manager.ts b/libraries/nestjs-libraries/src/integrations/integration.manager.ts index 021f1e6b..cb61de76 100644 --- a/libraries/nestjs-libraries/src/integrations/integration.manager.ts +++ b/libraries/nestjs-libraries/src/integrations/integration.manager.ts @@ -26,6 +26,7 @@ import { InstagramStandaloneProvider } from '@gitroom/nestjs-libraries/integrati import { FarcasterProvider } from '@gitroom/nestjs-libraries/integrations/social/farcaster.provider'; import { TelegramProvider } from '@gitroom/nestjs-libraries/integrations/social/telegram.provider'; import { NostrProvider } from '@gitroom/nestjs-libraries/integrations/social/nostr.provider'; +import { VkProvider } from '@gitroom/nestjs-libraries/integrations/social/vk.provider'; export const socialIntegrationList: SocialProvider[] = [ new XProvider(), @@ -48,6 +49,7 @@ export const socialIntegrationList: SocialProvider[] = [ new FarcasterProvider(), new TelegramProvider(), new NostrProvider(), + new VkProvider(), // new MastodonCustomProvider(), ]; diff --git a/libraries/nestjs-libraries/src/integrations/social/vk.provider.ts b/libraries/nestjs-libraries/src/integrations/social/vk.provider.ts new file mode 100644 index 00000000..aef318f7 --- /dev/null +++ b/libraries/nestjs-libraries/src/integrations/social/vk.provider.ts @@ -0,0 +1,246 @@ +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 { createHash, randomBytes } from 'crypto'; +import axios from 'axios'; +import FormDataNew from 'form-data'; +import mime from 'mime-types'; + +export class VkProvider extends SocialAbstract implements SocialProvider { + identifier = 'vk'; + name = 'VK'; + isBetweenSteps = false; + scopes = [ + 'vkid.personal_info', + 'email', + 'wall', + 'status', + 'docs', + 'photos', + 'video', + ]; + + async refreshToken(refresh_token: string): Promise { + return { + refreshToken: '', + expiresIn: 0, + accessToken: '', + id: '', + name: '', + picture: '', + username: '', + }; + } + + async generateAuthUrl() { + const state = makeId(32); + const codeVerifier = randomBytes(64).toString('base64url'); + const challenge = Buffer.from( + createHash('sha256').update(codeVerifier).digest() + ) + .toString('base64') + .replace(/=*$/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + + return { + url: + 'https://id.vk.com/authorize' + + `?response_type=code` + + `&client_id=${process.env.VK_ID}` + + `&code_challenge_method=S256` + + `&code_challenge=${challenge}` + + `&redirect_uri=${encodeURIComponent( + `${ + process?.env.FRONTEND_URL?.indexOf('https') == -1 + ? `https://redirectmeto.com/${process?.env.FRONTEND_URL}` + : `${process?.env.FRONTEND_URL}` + }/integrations/social/vk` + )}` + + `&state=${state}` + + `&scope=${encodeURIComponent(this.scopes.join(' '))}`, + codeVerifier, + state, + }; + } + + async authenticate(params: { + code: string; + codeVerifier: string; + refresh?: string; + }) { + const [code, device_id] = params.code.split('&&&&'); + + const formData = new FormData(); + formData.append('client_id', process.env.VK_ID!); + formData.append('grant_type', 'authorization_code'); + formData.append('code_verifier', params.codeVerifier); + formData.append('device_id', device_id); + formData.append('code', 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/vk` + ); + + const { access_token, scope, refresh_token } = await ( + await this.fetch('https://id.vk.com/oauth2/auth', { + method: 'POST', + body: formData, + }) + ).json(); + + const newFormData = new FormData(); + newFormData.append('client_id', process.env.VK_ID!); + newFormData.append('access_token', access_token); + + const { + user: { user_id, first_name, last_name, avatar }, + } = await ( + await this.fetch('https://id.vk.com/oauth2/user_info', { + method: 'POST', + body: newFormData, + }) + ).json(); + + return { + id: user_id, + name: first_name + ' ' + last_name, + accessToken: access_token, + refreshToken: access_token, + expiresIn: dayjs().add(59, 'days').unix() - dayjs().unix(), + picture: avatar, + username: first_name.toLowerCase(), + }; + } + + async post( + userId: string, + accessToken: string, + postDetails: PostDetails[] + ): Promise { + let replyTo = ''; + const values: PostResponse[] = []; + + const uploading = await Promise.all( + postDetails.map(async (post) => { + return await Promise.all( + (post?.media || []).map(async (media) => { + const all = await ( + await this.fetch( + media.url.indexOf('mp4') > -1 + ? `https://api.vk.com/method/video.save?access_token=${accessToken}&v=5.251` + : `https://api.vk.com/method/photos.getWallUploadServer?owner_id=${userId}&access_token=${accessToken}&v=5.251` + ) + ).json(); + + const { data } = await axios.get(media.url!, { + responseType: 'stream', + }); + + const slash = media.url.split('/').at(-1); + + const formData = new FormDataNew(); + formData.append('photo', data, { + filename: slash, + contentType: mime.lookup(slash!) || '', + }); + const value = ( + await axios.post(all.response.upload_url, formData, { + headers: { + ...formData.getHeaders(), + }, + }) + ).data; + + if (media.url.indexOf('mp4') > -1) { + return { + id: all.response.video_id, + type: 'video', + }; + } + + const formSend = new FormData(); + formSend.append('photo', value.photo); + formSend.append('server', value.server); + formSend.append('hash', value.hash); + + const { id } = ( + await ( + await fetch( + `https://api.vk.com/method/photos.saveWallPhoto?access_token=${accessToken}&v=5.251`, + { + method: 'POST', + body: formSend, + } + ) + ).json() + ).response[0]; + + return { + id, + type: 'photo', + }; + }) + ); + }) + ); + + let i = 0; + for (const post of postDetails) { + const list = (uploading?.[i] || []); + + const body = new FormData(); + body.append('message', post.message); + if (replyTo) { + body.append('post_id', replyTo); + } + + if (list.length) { + body.append( + 'attachments', + list.map((p) => `${p.type}${userId}_${p.id}`).join(',') + ); + } + + const { response, ...all } = await ( + await this.fetch( + `https://api.vk.com/method/${ + replyTo ? 'wall.createComment' : 'wall.post' + }?v=5.251&access_token=${accessToken}&client_id=${process.env.VK_ID}`, + { + method: 'POST', + body, + } + ) + ).json(); + + + values.push({ + id: post.id, + postId: String(response?.post_id || response?.comment_id), + releaseURL: `https://vk.com/feed?w=wall${userId}_${ + response?.post_id || replyTo + }`, + status: 'completed', + }); + + if (!replyTo) { + replyTo = response.post_id; + } + + i++; + } + + return values; + } +}