@classytic/social 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/LICENSE +21 -0
- package/README.md +368 -0
- package/dist/base-Bw7e52V8.mjs +246 -0
- package/dist/base-Bw7e52V8.mjs.map +1 -0
- package/dist/base-DBtKFiSX.d.mts +226 -0
- package/dist/base-DBtKFiSX.d.mts.map +1 -0
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/client/index.d.mts +44 -0
- package/dist/client/index.d.mts.map +1 -0
- package/dist/client/index.mjs +154 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/common/index.d.mts +3 -0
- package/dist/common/index.mjs +7 -0
- package/dist/contracts-Cdwa4zlg.d.mts +121 -0
- package/dist/contracts-Cdwa4zlg.d.mts.map +1 -0
- package/dist/contracts-lCa069IK.mjs +221 -0
- package/dist/contracts-lCa069IK.mjs.map +1 -0
- package/dist/env-Bl0cwwjC.mjs +955 -0
- package/dist/env-Bl0cwwjC.mjs.map +1 -0
- package/dist/env-DxOZHf0p.d.mts +394 -0
- package/dist/env-DxOZHf0p.d.mts.map +1 -0
- package/dist/errors-Cm6LeKf7.mjs +32 -0
- package/dist/errors-Cm6LeKf7.mjs.map +1 -0
- package/dist/facebook-l_4CghaA.mjs +95 -0
- package/dist/facebook-l_4CghaA.mjs.map +1 -0
- package/dist/http-DpcLSR1M.mjs +197 -0
- package/dist/http-DpcLSR1M.mjs.map +1 -0
- package/dist/index.d.mts +42 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +71 -0
- package/dist/index.mjs.map +1 -0
- package/dist/instagram-BGaeUFU2.mjs +90 -0
- package/dist/instagram-BGaeUFU2.mjs.map +1 -0
- package/dist/linkedin-70whtVKa.mjs +101 -0
- package/dist/linkedin-70whtVKa.mjs.map +1 -0
- package/dist/meta-D3vcJU1c.mjs +126 -0
- package/dist/meta-D3vcJU1c.mjs.map +1 -0
- package/dist/pkce-jq5II68b.mjs +72 -0
- package/dist/pkce-jq5II68b.mjs.map +1 -0
- package/dist/polling-DZ1apXtA.mjs +25 -0
- package/dist/polling-DZ1apXtA.mjs.map +1 -0
- package/dist/providers/facebook.d.mts +135 -0
- package/dist/providers/facebook.d.mts.map +1 -0
- package/dist/providers/facebook.mjs +450 -0
- package/dist/providers/facebook.mjs.map +1 -0
- package/dist/providers/instagram.d.mts +122 -0
- package/dist/providers/instagram.d.mts.map +1 -0
- package/dist/providers/instagram.mjs +496 -0
- package/dist/providers/instagram.mjs.map +1 -0
- package/dist/providers/linkedin.d.mts +145 -0
- package/dist/providers/linkedin.d.mts.map +1 -0
- package/dist/providers/linkedin.mjs +574 -0
- package/dist/providers/linkedin.mjs.map +1 -0
- package/dist/providers/reddit.d.mts +102 -0
- package/dist/providers/reddit.d.mts.map +1 -0
- package/dist/providers/reddit.mjs +657 -0
- package/dist/providers/reddit.mjs.map +1 -0
- package/dist/providers/telegram.d.mts +139 -0
- package/dist/providers/telegram.d.mts.map +1 -0
- package/dist/providers/telegram.mjs +517 -0
- package/dist/providers/telegram.mjs.map +1 -0
- package/dist/providers/tiktok.d.mts +116 -0
- package/dist/providers/tiktok.d.mts.map +1 -0
- package/dist/providers/tiktok.mjs +676 -0
- package/dist/providers/tiktok.mjs.map +1 -0
- package/dist/providers/twitter.d.mts +150 -0
- package/dist/providers/twitter.d.mts.map +1 -0
- package/dist/providers/twitter.mjs +628 -0
- package/dist/providers/twitter.mjs.map +1 -0
- package/dist/providers/whatsapp.d.mts +79 -0
- package/dist/providers/whatsapp.d.mts.map +1 -0
- package/dist/providers/whatsapp.mjs +376 -0
- package/dist/providers/whatsapp.mjs.map +1 -0
- package/dist/providers/youtube.d.mts +153 -0
- package/dist/providers/youtube.d.mts.map +1 -0
- package/dist/providers/youtube.mjs +902 -0
- package/dist/providers/youtube.mjs.map +1 -0
- package/dist/reddit-B10kS4Se.mjs +126 -0
- package/dist/reddit-B10kS4Se.mjs.map +1 -0
- package/dist/schemas/index.d.mts +819 -0
- package/dist/schemas/index.d.mts.map +1 -0
- package/dist/schemas/index.mjs +31 -0
- package/dist/schemas/index.mjs.map +1 -0
- package/dist/security-BXhfebWm.d.mts +338 -0
- package/dist/security-BXhfebWm.d.mts.map +1 -0
- package/dist/shared-Fvc6xQku.mjs +100 -0
- package/dist/shared-Fvc6xQku.mjs.map +1 -0
- package/dist/telegram-FaUHpZgB.mjs +107 -0
- package/dist/telegram-FaUHpZgB.mjs.map +1 -0
- package/dist/tiktok-B_bMk4G-.mjs +94 -0
- package/dist/tiktok-B_bMk4G-.mjs.map +1 -0
- package/dist/twitter-BC22zfuc.mjs +98 -0
- package/dist/twitter-BC22zfuc.mjs.map +1 -0
- package/dist/types-BFE4psYI.d.mts +102 -0
- package/dist/types-BFE4psYI.d.mts.map +1 -0
- package/dist/types-Bv27tcT0.d.mts +230 -0
- package/dist/types-Bv27tcT0.d.mts.map +1 -0
- package/dist/types-BwkKyqpi.d.mts +253 -0
- package/dist/types-BwkKyqpi.d.mts.map +1 -0
- package/dist/types-CJrHMDV9.mjs +27 -0
- package/dist/types-CJrHMDV9.mjs.map +1 -0
- package/dist/types-ClbVc2rc.d.mts +117 -0
- package/dist/types-ClbVc2rc.d.mts.map +1 -0
- package/dist/types-D91N16Ym.d.mts +242 -0
- package/dist/types-D91N16Ym.d.mts.map +1 -0
- package/dist/types-DfLp_ibQ.d.mts +178 -0
- package/dist/types-DfLp_ibQ.d.mts.map +1 -0
- package/dist/types-DfjDgEoJ.d.mts +88 -0
- package/dist/types-DfjDgEoJ.d.mts.map +1 -0
- package/dist/types-Dp5Z9VBr.mjs +23 -0
- package/dist/types-Dp5Z9VBr.mjs.map +1 -0
- package/dist/types-hriBJTsU.d.mts +129 -0
- package/dist/types-hriBJTsU.d.mts.map +1 -0
- package/dist/types-rn6UuLL8.d.mts +184 -0
- package/dist/types-rn6UuLL8.d.mts.map +1 -0
- package/dist/whatsapp-CFp7ryR4.mjs +101 -0
- package/dist/whatsapp-CFp7ryR4.mjs.map +1 -0
- package/dist/youtube-Bs0fdY7H.mjs +98 -0
- package/dist/youtube-Bs0fdY7H.mjs.map +1 -0
- package/package.json +148 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twitter.mjs","names":["ERROR_HINTS"],"sources":["../../src/providers/twitter/index.ts"],"sourcesContent":["/**\n * Twitter/X Provider\n * ==================\n * Full Twitter API v2 integration with OAuth 2.0 + PKCE.\n *\n * Supports:\n * - OAuth 2.0 with PKCE (authorization code flow)\n * - Tweet CRUD (create, delete, search, get)\n * - Reply and quote tweets\n * - Like / unlike / retweet / unretweet\n * - User lookup (by ID, username, or authenticated user)\n * - Direct messages (send)\n * - Followers / following lists\n * - Bookmarks\n * - Media upload (via v1.1 chunked upload)\n *\n * @see https://developer.x.com/en/docs/x-api\n */\n\nimport { PlatformProvider, type AccountInfo, type OAuthTokens, type UploadResult, type TestResult, type CredentialField, type ProviderMetadata, type AuthUrlOptions, type ProviderConfig } from '../../base.js';\nimport { SocialError } from '../../errors.js';\nimport { PkceStore, generateCodeVerifier, generateCodeChallenge } from '../../common/oauth/index.js';\nimport { httpRequest } from '../../common/http.js';\nimport { TwitterCredentialsSchema } from '../../schemas/twitter.js';\nimport type { z } from 'zod';\nimport type {\n TwitterCredentials,\n CreateTweetParams,\n CreateTweetResult,\n TweetSearchParams,\n TweetSearchResult,\n Tweet,\n TwitterUser,\n SendDmParams,\n SendDmResult,\n TweetField,\n UserField,\n TweetExpansion,\n TWITTER_ERROR_HINTS,\n} from './types.js';\nimport { TWITTER_ERROR_HINTS as ERROR_HINTS } from './types.js';\n\nexport type { TwitterCredentials, CreateTweetParams, CreateTweetResult, TweetSearchParams, TweetSearchResult, Tweet, TwitterUser, SendDmParams, SendDmResult } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst API_BASE = 'https://api.twitter.com/2';\nconst AUTH_URL = 'https://x.com/i/oauth2/authorize';\nconst TOKEN_URL = 'https://api.twitter.com/2/oauth2/token';\nconst REVOKE_URL = 'https://api.twitter.com/2/oauth2/revoke';\n\nconst DEFAULT_SCOPES = [\n 'tweet.read', 'tweet.write', 'tweet.moderate.write',\n 'users.read', 'follows.read', 'follows.write',\n 'offline.access', 'like.read', 'like.write',\n 'dm.write', 'dm.read', 'list.read', 'list.write',\n 'bookmark.read', 'bookmark.write',\n 'block.read', 'mute.read', 'media.write',\n];\n\nconst SCOPE_DESCRIPTIONS: Record<string, string> = {\n 'tweet.read': 'Read tweets on your behalf',\n 'tweet.write': 'Create, delete, and manage tweets',\n 'tweet.moderate.write': 'Hide/unhide replies to your tweets',\n 'users.read': 'Read user profile information',\n 'follows.read': 'View followers and following lists',\n 'follows.write': 'Follow and unfollow users',\n 'offline.access': 'Stay connected (refresh tokens)',\n 'like.read': 'View liked tweets',\n 'like.write': 'Like and unlike tweets',\n 'dm.write': 'Send direct messages',\n 'dm.read': 'Read direct messages',\n 'list.read': 'Read lists',\n 'list.write': 'Create and manage lists',\n 'bookmark.read': 'View bookmarks',\n 'bookmark.write': 'Create and manage bookmarks',\n 'block.read': 'View blocked accounts',\n 'mute.read': 'View muted accounts',\n};\n\n// ---------------------------------------------------------------------------\n// PKCE store\n// ---------------------------------------------------------------------------\n// TTL'd in-memory verifier store. For multi-instance deployments, persist the\n// verifier alongside `state` (e.g., in a session/Redis) and pass it back to\n// `exchangeCode(code, creds, state)` — entries here are evicted after 10 min.\n\nconst pkceVerifiers = new PkceStore(10 * 60 * 1000);\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport class TwitterProvider extends PlatformProvider {\n constructor(config: ProviderConfig = {}) {\n super(config);\n this.name = 'twitter';\n this.displayName = 'Twitter / X';\n this.authType = 'oauth2';\n }\n\n // ─── Auth ────────────────────────────────────────────────────────────\n\n getAuthUrl(state: string, credData?: Record<string, any>, options?: AuthUrlOptions): string {\n const clientId = credData?.clientId;\n if (!clientId) throw new SocialError('twitter', 'Client ID is required', { statusCode: 400 });\n\n const redirectUri = credData?.redirectUri || this.config.redirectUri\n || `http://localhost:${this.config.port || 8060}/api/oauth/twitter/callback`;\n\n const codeVerifier = generateCodeVerifier();\n pkceVerifiers.set(state, codeVerifier);\n\n // Generate challenge synchronously isn't possible with subtle crypto,\n // so we use plain verifier as challenge with method=plain as fallback.\n // For proper S256, use getAuthUrlAsync().\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: clientId,\n redirect_uri: redirectUri,\n scope: DEFAULT_SCOPES.join(' '),\n state,\n code_challenge: codeVerifier,\n code_challenge_method: 'plain',\n });\n\n return `${AUTH_URL}?${params.toString()}`;\n }\n\n /**\n * Async auth URL generation with proper S256 PKCE challenge.\n * Prefer this over getAuthUrl() when async is acceptable.\n */\n async getAuthUrlAsync(state: string, credData?: Record<string, any>): Promise<string> {\n const clientId = credData?.clientId;\n if (!clientId) throw new SocialError('twitter', 'Client ID is required', { statusCode: 400 });\n\n const redirectUri = credData?.redirectUri || this.config.redirectUri\n || `http://localhost:${this.config.port || 8060}/api/oauth/twitter/callback`;\n\n const codeVerifier = generateCodeVerifier();\n pkceVerifiers.set(state, codeVerifier);\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: clientId,\n redirect_uri: redirectUri,\n scope: DEFAULT_SCOPES.join(' '),\n state,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n });\n\n return `${AUTH_URL}?${params.toString()}`;\n }\n\n async exchangeCode(code: string, credData?: Record<string, any>, state?: string): Promise<OAuthTokens> {\n const clientId = credData?.clientId;\n const clientSecret = credData?.clientSecret;\n if (!clientId) throw new SocialError('twitter', 'Client ID is required for token exchange', { statusCode: 400 });\n\n const redirectUri = credData?.redirectUri || this.config.redirectUri\n || `http://localhost:${this.config.port || 8060}/api/oauth/twitter/callback`;\n\n // `take()` returns and removes in one call (single-use semantics).\n const codeVerifier = state ? pkceVerifiers.take(state) : undefined;\n\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n redirect_uri: redirectUri,\n client_id: clientId,\n ...(codeVerifier ? { code_verifier: codeVerifier } : {}),\n });\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n\n // Use Basic auth if client secret is available (confidential client)\n if (clientSecret) {\n headers['Authorization'] = `Basic ${btoa(`${clientId}:${clientSecret}`)}`;\n }\n\n const response = await fetch(TOKEN_URL, { method: 'POST', headers, body: body.toString() });\n const data = await response.json() as Record<string, unknown>;\n\n if (!response.ok) {\n throw new SocialError('twitter', `Token exchange failed: ${data.error_description || data.error || response.statusText}`, {\n statusCode: response.status,\n errorCode: data.error as string,\n hint: 'Ensure your Client ID, Client Secret, and redirect URI are correct.',\n originalError: new Error(JSON.stringify(data)),\n });\n }\n\n return {\n access_token: data.access_token as string,\n refresh_token: data.refresh_token as string | undefined,\n expires_in: data.expires_in as number | undefined,\n token_type: data.token_type as string | undefined,\n scope: data.scope as string | undefined,\n };\n }\n\n async refreshToken(refreshToken: string, credData?: Record<string, any>): Promise<OAuthTokens> {\n const clientId = credData?.clientId;\n const clientSecret = credData?.clientSecret;\n if (!clientId) throw new SocialError('twitter', 'Client ID is required for token refresh', { statusCode: 400 });\n\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: clientId,\n });\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n\n if (clientSecret) {\n headers['Authorization'] = `Basic ${btoa(`${clientId}:${clientSecret}`)}`;\n }\n\n const response = await fetch(TOKEN_URL, { method: 'POST', headers, body: body.toString() });\n const data = await response.json() as Record<string, unknown>;\n\n if (!response.ok) {\n throw new SocialError('twitter', `Token refresh failed: ${data.error_description || data.error || response.statusText}`, {\n statusCode: response.status,\n errorCode: data.error as string,\n hint: 'The refresh token may have been revoked. Re-authenticate.',\n });\n }\n\n return {\n access_token: data.access_token as string,\n refresh_token: data.refresh_token as string | undefined,\n expires_in: data.expires_in as number | undefined,\n token_type: data.token_type as string | undefined,\n scope: data.scope as string | undefined,\n };\n }\n\n async revokeToken(accessToken: string, credData?: Record<string, any>): Promise<void> {\n const clientId = credData?.clientId;\n if (!clientId) return;\n\n const body = new URLSearchParams({\n token: accessToken,\n token_type_hint: 'access_token',\n client_id: clientId,\n });\n\n await fetch(REVOKE_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n }).catch(() => {}); // Best-effort revocation\n }\n\n // ─── Account ─────────────────────────────────────────────────────────\n\n async getAccountInfo(accessToken: string): Promise<AccountInfo> {\n const data = await this._api('GET', '/users/me?user.fields=id,name,username,description,profile_image_url,public_metrics,verified,verified_type,created_at,location,url', accessToken);\n\n const user = data.data;\n return {\n id: user.id,\n name: user.name,\n username: user.username,\n profileImage: user.profile_image_url ?? null,\n description: user.description,\n location: user.location,\n url: user.url,\n verified: user.verified,\n verifiedType: user.verified_type,\n followersCount: user.public_metrics?.followers_count,\n followingCount: user.public_metrics?.following_count,\n tweetCount: user.public_metrics?.tweet_count,\n listedCount: user.public_metrics?.listed_count,\n createdAt: user.created_at,\n };\n }\n\n async testCredential(credentialData: Record<string, any>): Promise<TestResult> {\n try {\n const tokenData = typeof credentialData.oauthTokenData === 'string'\n ? JSON.parse(credentialData.oauthTokenData)\n : credentialData.oauthTokenData;\n\n if (!tokenData?.access_token) {\n return { status: 'Error', message: 'No access token found. Complete OAuth authorization first.' };\n }\n\n const info = await this.getAccountInfo(tokenData.access_token);\n return {\n status: 'OK',\n message: `Connected as @${info.username} (${info.name})`,\n data: { id: info.id, username: info.username, name: info.name },\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { status: 'Error', message: msg };\n }\n }\n\n // ─── Tweet CRUD ──────────────────────────────────────────────────────\n\n /**\n * Create a tweet (or reply / quote tweet).\n */\n async createTweet(accessToken: string, params: CreateTweetParams): Promise<CreateTweetResult> {\n const body: Record<string, unknown> = { text: params.text };\n\n if (params.replyTo) {\n body.reply = { in_reply_to_tweet_id: params.replyTo };\n }\n if (params.quoteTweetId) {\n body.quote_tweet_id = params.quoteTweetId;\n }\n if (params.mediaIds?.length) {\n body.media = { media_ids: params.mediaIds };\n }\n if (params.poll) {\n body.poll = { options: params.poll.options, duration_minutes: params.poll.durationMinutes };\n }\n if (params.replySettings) {\n body.reply_settings = params.replySettings;\n }\n\n const result = await this._api('POST', '/tweets', accessToken, body);\n return {\n id: result.data.id,\n text: result.data.text,\n editHistoryTweetIds: result.data.edit_history_tweet_ids,\n };\n }\n\n /**\n * Delete a tweet by ID.\n */\n async deleteTweet(accessToken: string, tweetId: string): Promise<{ deleted: boolean }> {\n const result = await this._api('DELETE', `/tweets/${tweetId}`, accessToken);\n return { deleted: result.data?.deleted ?? true };\n }\n\n /**\n * Get a single tweet by ID.\n */\n async getTweet(accessToken: string, tweetId: string, fields?: TweetField[]): Promise<Tweet> {\n const tweetFields = fields?.join(',') || 'id,text,author_id,created_at,public_metrics,entities,conversation_id,reply_settings,source,lang';\n const data = await this._api('GET', `/tweets/${tweetId}?tweet.fields=${tweetFields}`, accessToken);\n\n if (!data.data) {\n throw new SocialError('twitter', `Tweet ${tweetId} not found`, {\n statusCode: 404,\n errorCode: 'TWEET_NOT_FOUND',\n hint: ERROR_HINTS['TWEET_NOT_FOUND'],\n });\n }\n\n return this._parseTweet(data.data);\n }\n\n /**\n * Search recent tweets (last 7 days on Basic tier).\n */\n async searchTweets(accessToken: string, params: TweetSearchParams): Promise<TweetSearchResult> {\n const qs = new URLSearchParams({ query: params.query });\n\n if (params.maxResults) qs.set('max_results', String(params.maxResults));\n if (params.nextToken) qs.set('next_token', params.nextToken);\n if (params.startTime) qs.set('start_time', params.startTime);\n if (params.endTime) qs.set('end_time', params.endTime);\n if (params.sortOrder) qs.set('sort_order', params.sortOrder);\n if (params.tweetFields?.length) qs.set('tweet.fields', params.tweetFields.join(','));\n if (params.userFields?.length) qs.set('user.fields', params.userFields.join(','));\n if (params.expansions?.length) qs.set('expansions', params.expansions.join(','));\n\n // Defaults\n if (!params.tweetFields?.length) {\n qs.set('tweet.fields', 'id,text,author_id,created_at,public_metrics,entities,source,lang');\n }\n\n const result = await this._api('GET', `/tweets/search/recent?${qs.toString()}`, accessToken);\n\n return {\n tweets: (result.data || []).map((t: Record<string, unknown>) => this._parseTweet(t)),\n meta: {\n newestId: result.meta?.newest_id as string | undefined,\n oldestId: result.meta?.oldest_id as string | undefined,\n resultCount: (result.meta?.result_count as number) || 0,\n nextToken: result.meta?.next_token as string | undefined,\n },\n includes: result.includes,\n };\n }\n\n // ─── Engagement ──────────────────────────────────────────────────────\n\n /**\n * Like a tweet.\n */\n async likeTweet(accessToken: string, tweetId: string): Promise<{ liked: boolean }> {\n const me = await this._getMyId(accessToken);\n const result = await this._api('POST', `/users/${me}/likes`, accessToken, { tweet_id: tweetId });\n return { liked: result.data?.liked ?? true };\n }\n\n /**\n * Unlike a tweet.\n */\n async unlikeTweet(accessToken: string, tweetId: string): Promise<{ liked: boolean }> {\n const me = await this._getMyId(accessToken);\n const result = await this._api('DELETE', `/users/${me}/likes/${tweetId}`, accessToken);\n return { liked: result.data?.liked ?? false };\n }\n\n /**\n * Retweet a tweet.\n */\n async retweet(accessToken: string, tweetId: string): Promise<{ retweeted: boolean }> {\n const me = await this._getMyId(accessToken);\n const result = await this._api('POST', `/users/${me}/retweets`, accessToken, { tweet_id: tweetId });\n return { retweeted: result.data?.retweeted ?? true };\n }\n\n /**\n * Remove a retweet.\n */\n async unretweet(accessToken: string, tweetId: string): Promise<{ retweeted: boolean }> {\n const me = await this._getMyId(accessToken);\n const result = await this._api('DELETE', `/users/${me}/retweets/${tweetId}`, accessToken);\n return { retweeted: result.data?.retweeted ?? false };\n }\n\n /**\n * Bookmark a tweet.\n */\n async bookmarkTweet(accessToken: string, tweetId: string): Promise<{ bookmarked: boolean }> {\n const me = await this._getMyId(accessToken);\n const result = await this._api('POST', `/users/${me}/bookmarks`, accessToken, { tweet_id: tweetId });\n return { bookmarked: result.data?.bookmarked ?? true };\n }\n\n /**\n * Remove a bookmark.\n */\n async removeBookmark(accessToken: string, tweetId: string): Promise<{ bookmarked: boolean }> {\n const me = await this._getMyId(accessToken);\n const result = await this._api('DELETE', `/users/${me}/bookmarks/${tweetId}`, accessToken);\n return { bookmarked: result.data?.bookmarked ?? false };\n }\n\n // ─── Social (Follow / Unfollow) ──────────────────────────────────────\n\n /**\n * Follow a user.\n */\n async followUser(accessToken: string, targetUserId: string): Promise<{ following: boolean; pendingFollow: boolean }> {\n const me = await this._getMyId(accessToken);\n const result = await this._api('POST', `/users/${me}/following`, accessToken, { target_user_id: targetUserId });\n return {\n following: result.data?.following ?? true,\n pendingFollow: result.data?.pending_follow ?? false,\n };\n }\n\n /**\n * Unfollow a user.\n */\n async unfollowUser(accessToken: string, targetUserId: string): Promise<{ following: boolean }> {\n const me = await this._getMyId(accessToken);\n const result = await this._api('DELETE', `/users/${me}/following/${targetUserId}`, accessToken);\n return { following: result.data?.following ?? false };\n }\n\n /**\n * Get followers of a user.\n */\n async getFollowers(\n accessToken: string,\n userId: string,\n opts: { maxResults?: number; paginationToken?: string; userFields?: UserField[] } = {},\n ): Promise<{ users: TwitterUser[]; nextToken?: string }> {\n const qs = new URLSearchParams();\n if (opts.maxResults) qs.set('max_results', String(opts.maxResults));\n if (opts.paginationToken) qs.set('pagination_token', opts.paginationToken);\n qs.set('user.fields', (opts.userFields || ['id', 'name', 'username', 'profile_image_url', 'public_metrics', 'verified']).join(','));\n\n const result = await this._api('GET', `/users/${userId}/followers?${qs.toString()}`, accessToken);\n return {\n users: (result.data || []).map((u: Record<string, unknown>) => this._parseUser(u)),\n nextToken: result.meta?.next_token as string | undefined,\n };\n }\n\n /**\n * Get users that a user is following.\n */\n async getFollowing(\n accessToken: string,\n userId: string,\n opts: { maxResults?: number; paginationToken?: string; userFields?: UserField[] } = {},\n ): Promise<{ users: TwitterUser[]; nextToken?: string }> {\n const qs = new URLSearchParams();\n if (opts.maxResults) qs.set('max_results', String(opts.maxResults));\n if (opts.paginationToken) qs.set('pagination_token', opts.paginationToken);\n qs.set('user.fields', (opts.userFields || ['id', 'name', 'username', 'profile_image_url', 'public_metrics', 'verified']).join(','));\n\n const result = await this._api('GET', `/users/${userId}/following?${qs.toString()}`, accessToken);\n return {\n users: (result.data || []).map((u: Record<string, unknown>) => this._parseUser(u)),\n nextToken: result.meta?.next_token as string | undefined,\n };\n }\n\n // ─── User Lookup ─────────────────────────────────────────────────────\n\n /**\n * Get a user by username.\n */\n async getUserByUsername(accessToken: string, username: string, fields?: UserField[]): Promise<TwitterUser> {\n const clean = username.replace(/^@/, '');\n const userFields = fields?.join(',') || 'id,name,username,description,profile_image_url,public_metrics,verified,verified_type,created_at,location,url';\n const data = await this._api('GET', `/users/by/username/${clean}?user.fields=${userFields}`, accessToken);\n\n if (!data.data) {\n throw new SocialError('twitter', `User @${clean} not found`, {\n statusCode: 404,\n errorCode: 'USER_NOT_FOUND',\n hint: ERROR_HINTS['USER_NOT_FOUND'],\n });\n }\n\n return this._parseUser(data.data);\n }\n\n /**\n * Get a user by ID.\n */\n async getUserById(accessToken: string, userId: string, fields?: UserField[]): Promise<TwitterUser> {\n const userFields = fields?.join(',') || 'id,name,username,description,profile_image_url,public_metrics,verified,verified_type,created_at,location,url';\n const data = await this._api('GET', `/users/${userId}?user.fields=${userFields}`, accessToken);\n\n if (!data.data) {\n throw new SocialError('twitter', `User ${userId} not found`, {\n statusCode: 404,\n errorCode: 'USER_NOT_FOUND',\n hint: ERROR_HINTS['USER_NOT_FOUND'],\n });\n }\n\n return this._parseUser(data.data);\n }\n\n // ─── Direct Messages ─────────────────────────────────────────────────\n\n /**\n * Send a direct message to a user.\n */\n async sendDirectMessage(accessToken: string, params: SendDmParams): Promise<SendDmResult> {\n const body: Record<string, unknown> = { text: params.text };\n if (params.mediaId) {\n body.attachments = [{ media_id: params.mediaId }];\n }\n\n const result = await this._api('POST', `/dm_conversations/with/${params.participantId}/messages`, accessToken, body);\n return {\n dmConversationId: result.data?.dm_conversation_id ?? '',\n dmEventId: result.data?.dm_event_id ?? '',\n };\n }\n\n // ─── Mute / Block ────────────────────────────────────────────────────\n\n /**\n * Mute a user.\n */\n async muteUser(accessToken: string, targetUserId: string): Promise<{ muting: boolean }> {\n const me = await this._getMyId(accessToken);\n const result = await this._api('POST', `/users/${me}/muting`, accessToken, { target_user_id: targetUserId });\n return { muting: result.data?.muting ?? true };\n }\n\n /**\n * Unmute a user.\n */\n async unmuteUser(accessToken: string, targetUserId: string): Promise<{ muting: boolean }> {\n const me = await this._getMyId(accessToken);\n const result = await this._api('DELETE', `/users/${me}/muting/${targetUserId}`, accessToken);\n return { muting: result.data?.muting ?? false };\n }\n\n // ─── Upload (base interface stubs) ───────────────────────────────────\n\n async uploadPhoto(params: any): Promise<UploadResult> {\n // Twitter photo upload: create tweet with text and image URL\n // Media upload v1.1 is needed for actual binary upload, which requires OAuth 1.0a\n // For v2 flow: use createTweet with pre-uploaded media IDs\n const { tokens, caption, title } = params;\n const text = caption || title || '';\n\n if (!text) {\n throw new SocialError('twitter', 'Tweet text or caption is required', { statusCode: 400 });\n }\n\n const result = await this.createTweet(tokens.access_token, { text });\n return {\n platformPostId: result.id,\n platformUrl: `https://x.com/i/status/${result.id}`,\n status: 'published',\n uploadedAt: new Date(),\n };\n }\n\n async deletePost(accessToken: string, tweetId: string): Promise<unknown> {\n return this.deleteTweet(accessToken as string, tweetId as string);\n }\n\n async sendMessage(accessToken: string, participantId: string, text: string): Promise<unknown> {\n return this.sendDirectMessage(accessToken, { participantId, text });\n }\n\n // ─── Credential Schema & Metadata ────────────────────────────────────\n\n getCredentialZodSchema(): z.ZodType {\n return TwitterCredentialsSchema;\n }\n\n getCredentialSchema(): CredentialField[] {\n return [\n {\n name: 'clientId',\n displayName: 'Client ID',\n type: 'text',\n required: true,\n description: 'OAuth 2.0 Client ID from the Twitter Developer Portal',\n placeholder: 'abc123...',\n },\n {\n name: 'clientSecret',\n displayName: 'Client Secret',\n type: 'password',\n required: true,\n description: 'OAuth 2.0 Client Secret (for confidential clients)',\n placeholder: 'secret...',\n },\n ];\n }\n\n getMetadata(): ProviderMetadata {\n return {\n name: this.name,\n displayName: this.displayName,\n authType: this.authType,\n icon: 'twitter',\n brandColor: '#000000',\n description: 'Post tweets, reply, search, and manage your X/Twitter account',\n scopes: DEFAULT_SCOPES,\n scopeDescriptions: SCOPE_DESCRIPTIONS,\n setupGuide: [\n { step: 1, title: 'Create a Developer App', description: 'Go to developer.x.com → Projects & Apps → Create a new app. Select \"Web App\" as the app type.' },\n { step: 2, title: 'Set up OAuth 2.0', description: 'Under \"User authentication settings\", enable OAuth 2.0. Set the callback URL to your redirect URI. Select \"Web App\" type.' },\n { step: 3, title: 'Copy credentials', description: 'Copy the Client ID and Client Secret from the \"Keys and tokens\" tab.' },\n { step: 4, title: 'API Access Level', description: 'Ensure your app has at least Basic API access (Free tier is very limited). Apply for Pro if you need search and analytics.' },\n ],\n supportsScheduling: false,\n supportsEnvironment: false,\n redirectUriPattern: '/api/oauth/twitter/callback',\n credentialSchema: this.getCredentialSchema(),\n };\n }\n\n // ─── Private Helpers ─────────────────────────────────────────────────\n\n /** Cached user ID for the authenticated user (per-request) */\n private _myIdCache: Map<string, string> = new Map();\n\n private async _getMyId(accessToken: string): Promise<string> {\n const cached = this._myIdCache.get(accessToken);\n if (cached) return cached;\n\n const data = await this._api('GET', '/users/me', accessToken);\n const id = data.data.id as string;\n this._myIdCache.set(accessToken, id);\n return id;\n }\n\n /**\n * Core API request helper.\n */\n private async _api(method: string, endpoint: string, accessToken: string, body?: Record<string, unknown>): Promise<any> {\n const url = endpoint.startsWith('http') ? endpoint : `${API_BASE}${endpoint}`;\n\n const result = await httpRequest<any>('twitter', {\n method: method.toUpperCase() as 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',\n url,\n bearer: accessToken,\n headers: { 'User-Agent': 'web:com.classytic.social:v0.1.0' },\n json: body,\n timeout: 30_000,\n retry: { attempts: 2 },\n parseError: (raw, status) => {\n if (raw && typeof raw === 'object') {\n const r = raw as Record<string, unknown>;\n const errors = r.errors as Array<Record<string, unknown>> | undefined;\n const detail = errors?.[0]?.detail || r.detail || r.error_description || r.error;\n const errorTitle = (errors?.[0]?.title || r.title || 'API_ERROR') as string;\n const hintKey = errorTitle.toUpperCase().replace(/ /g, '_');\n return {\n message: `Twitter API error (${status}): ${detail || 'Unknown'}`,\n errorCode: errorTitle,\n hint: ERROR_HINTS[hintKey] || ERROR_HINTS[String(status)] || null,\n };\n }\n return null;\n },\n });\n\n if (result.status === 204) return { data: { deleted: true } };\n return result.data;\n }\n\n private _parseTweet(raw: Record<string, unknown>): Tweet {\n return {\n id: raw.id as string,\n text: raw.text as string,\n authorId: raw.author_id as string | undefined,\n createdAt: raw.created_at as string | undefined,\n conversationId: raw.conversation_id as string | undefined,\n inReplyToUserId: raw.in_reply_to_user_id as string | undefined,\n publicMetrics: raw.public_metrics\n ? {\n retweetCount: (raw.public_metrics as any).retweet_count ?? 0,\n replyCount: (raw.public_metrics as any).reply_count ?? 0,\n likeCount: (raw.public_metrics as any).like_count ?? 0,\n quoteCount: (raw.public_metrics as any).quote_count ?? 0,\n bookmarkCount: (raw.public_metrics as any).bookmark_count,\n impressionCount: (raw.public_metrics as any).impression_count,\n }\n : undefined,\n entities: raw.entities as Tweet['entities'],\n attachments: raw.attachments as Tweet['attachments'],\n source: raw.source as string | undefined,\n lang: raw.lang as string | undefined,\n replySettings: raw.reply_settings as Tweet['replySettings'],\n editHistoryTweetIds: raw.edit_history_tweet_ids as string[] | undefined,\n };\n }\n\n private _parseUser(raw: Record<string, unknown>): TwitterUser {\n return {\n id: raw.id as string,\n name: raw.name as string,\n username: raw.username as string,\n createdAt: raw.created_at as string | undefined,\n description: raw.description as string | undefined,\n location: raw.location as string | undefined,\n profileImageUrl: raw.profile_image_url as string | undefined,\n protected: raw.protected as boolean | undefined,\n publicMetrics: raw.public_metrics\n ? {\n followersCount: (raw.public_metrics as any).followers_count ?? 0,\n followingCount: (raw.public_metrics as any).following_count ?? 0,\n tweetCount: (raw.public_metrics as any).tweet_count ?? 0,\n listedCount: (raw.public_metrics as any).listed_count ?? 0,\n likeCount: (raw.public_metrics as any).like_count,\n }\n : undefined,\n url: raw.url as string | undefined,\n verified: raw.verified as boolean | undefined,\n verifiedType: raw.verified_type as TwitterUser['verifiedType'],\n pinnedTweetId: raw.pinned_tweet_id as string | undefined,\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { d as ProviderMetadata, i as CredentialField, l as ProviderConfig, p as TestResult, s as PlatformProvider } from "../base-DBtKFiSX.mjs";
|
|
2
|
+
import { C as UploadMediaResult, E as WhatsAppSendResult, S as TemplateStatus, T as WhatsAppMessageType, _ as SendVideoParams, a as MediaMessage, b as TemplateComponent, c as MessageTemplate, d as SendContactParams, f as SendDocumentParams, g as SendReactionParams, h as SendLocationParams, i as LocationMessage, l as PhoneNumber, m as SendInteractiveParams, n as CreateTemplateParams, o as MediaUrlResult, p as SendImageParams, r as InteractiveMessage, s as MessagePayload, t as ContactMessage, u as ReactionMessage, v as TemplateButton, w as WhatsAppCredentialData, x as TemplateMessage, y as TemplateCategory } from "../types-BwkKyqpi.mjs";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
//#region src/providers/whatsapp/index.d.ts
|
|
6
|
+
declare class WhatsAppProvider extends PlatformProvider {
|
|
7
|
+
constructor(config?: ProviderConfig);
|
|
8
|
+
/**
|
|
9
|
+
* Shared fetch helper for Meta Graph API. Delegates to `httpRequest` for
|
|
10
|
+
* timeout, retry on 429 / 5xx, and Retry-After honoring.
|
|
11
|
+
* @private
|
|
12
|
+
*/
|
|
13
|
+
private _apiRequest;
|
|
14
|
+
private _apiRequestFormData;
|
|
15
|
+
/**
|
|
16
|
+
* Get account information (first phone number from business account)
|
|
17
|
+
*/
|
|
18
|
+
getAccountInfo(accessToken: string, credentials?: Record<string, any>): Promise<{
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
profileImage: null;
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
24
|
+
* Test credential validity by verifying phone numbers endpoint
|
|
25
|
+
*/
|
|
26
|
+
testCredential(credentialData: Record<string, any>): Promise<TestResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Get phone numbers for a business account
|
|
29
|
+
*/
|
|
30
|
+
getPhoneNumbers(accessToken: string, businessAccountId: string): Promise<PhoneNumber[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Get message templates for a business account (lightweight, for dropdowns)
|
|
33
|
+
*/
|
|
34
|
+
getTemplates(accessToken: string, businessAccountId: string): Promise<MessageTemplate[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Get templates with full component details (for template management)
|
|
37
|
+
*/
|
|
38
|
+
getTemplatesDetailed(accessToken: string, businessAccountId: string): Promise<MessageTemplate[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Create a new message template
|
|
41
|
+
*/
|
|
42
|
+
createTemplate(accessToken: string, businessAccountId: string, templateData: CreateTemplateParams): Promise<unknown>;
|
|
43
|
+
/**
|
|
44
|
+
* Delete a message template by name
|
|
45
|
+
*/
|
|
46
|
+
deleteTemplate(accessToken: string, businessAccountId: string, templateName: string): Promise<any>;
|
|
47
|
+
/**
|
|
48
|
+
* Send a WhatsApp message
|
|
49
|
+
*/
|
|
50
|
+
sendMessage(accessToken: string, phoneNumberId: string, payload: MessagePayload): Promise<any>;
|
|
51
|
+
uploadMedia(accessToken: string, phoneNumberId: string, fileBuffer: Buffer, mimeType: string, fileName?: string): Promise<UploadMediaResult>;
|
|
52
|
+
getMediaUrl(accessToken: string, mediaId: string): Promise<MediaUrlResult>;
|
|
53
|
+
deleteMedia(accessToken: string, mediaId: string): Promise<{
|
|
54
|
+
success: boolean;
|
|
55
|
+
}>;
|
|
56
|
+
sendImage(accessToken: string, phoneNumberId: string, params: SendImageParams): Promise<WhatsAppSendResult>;
|
|
57
|
+
sendVideo(accessToken: string, phoneNumberId: string, params: SendVideoParams): Promise<WhatsAppSendResult>;
|
|
58
|
+
sendDocument(accessToken: string, phoneNumberId: string, params: SendDocumentParams): Promise<WhatsAppSendResult>;
|
|
59
|
+
sendLocation(accessToken: string, phoneNumberId: string, params: SendLocationParams): Promise<WhatsAppSendResult>;
|
|
60
|
+
sendContact(accessToken: string, phoneNumberId: string, params: SendContactParams): Promise<WhatsAppSendResult>;
|
|
61
|
+
sendInteractive(accessToken: string, phoneNumberId: string, params: SendInteractiveParams): Promise<WhatsAppSendResult>;
|
|
62
|
+
sendReaction(accessToken: string, phoneNumberId: string, params: SendReactionParams): Promise<WhatsAppSendResult>;
|
|
63
|
+
markAsRead(accessToken: string, phoneNumberId: string, messageId: string): Promise<{
|
|
64
|
+
success: boolean;
|
|
65
|
+
}>;
|
|
66
|
+
static cleanPhoneNumber(phone: string): string;
|
|
67
|
+
getCredentialZodSchema(): z.ZodType;
|
|
68
|
+
/**
|
|
69
|
+
* Get credential schema for UI
|
|
70
|
+
*/
|
|
71
|
+
getCredentialSchema(): CredentialField[];
|
|
72
|
+
/**
|
|
73
|
+
* Get provider metadata for frontend display
|
|
74
|
+
*/
|
|
75
|
+
getMetadata(): ProviderMetadata;
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { type ContactMessage, type CreateTemplateParams, type InteractiveMessage, type LocationMessage, type MediaMessage, type MediaUrlResult, type MessagePayload, type MessageTemplate, type PhoneNumber, type ReactionMessage, type SendContactParams, type SendDocumentParams, type SendImageParams, type SendInteractiveParams, type SendLocationParams, type SendReactionParams, type SendVideoParams, type TemplateButton, type TemplateCategory, type TemplateComponent, type TemplateMessage, type TemplateStatus, type UploadMediaResult, type WhatsAppCredentialData, type WhatsAppMessageType, WhatsAppProvider, type WhatsAppSendResult };
|
|
79
|
+
//# sourceMappingURL=whatsapp.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp.d.mts","names":[],"sources":["../../src/providers/whatsapp/index.ts"],"mappings":";;;;;cA2Ga,gBAAA,SAAyB,gBAAA;cACxB,MAAA,GAAQ,cAAA;EAuKJ;;;;;EAAA,QA3JF,WAAA;EAAA,QAwBA,mBAAA;EAyK0G;;;EAhJlH,cAAA,CAAe,WAAA,UAAqB,WAAA,GAAa,MAAA,gBAA2B,OAAA;IAAU,EAAA;IAAY,IAAA;IAAc,YAAA;EAAA;EA2KxB;;;EAhJxF,cAAA,CAAe,cAAA,EAAgB,MAAA,gBAAsB,OAAA,CAAQ,UAAA;EAwJyB;;;EAhHtF,eAAA,CAAgB,WAAA,UAAqB,iBAAA,WAA4B,OAAA,CAAQ,WAAA;EAgIT;;;EApHhE,YAAA,CAAa,WAAA,UAAqB,iBAAA,WAA4B,OAAA,CAAQ,eAAA;EA4H8B;;;EAhHpG,oBAAA,CAAqB,WAAA,UAAqB,iBAAA,WAA4B,OAAA,CAAQ,eAAA;EAwHQ;;;EA5GtF,cAAA,CACJ,WAAA,UACA,iBAAA,UACA,YAAA,EAAc,oBAAA,GACb,OAAA;EA2JY;;;EA/IT,cAAA,CAAe,WAAA,UAAqB,iBAAA,UAA2B,YAAA,WAAuB,OAAA;EArLxD;;;EAgM9B,WAAA,CAAY,WAAA,UAAqB,aAAA,UAAuB,OAAA,EAAS,cAAA,GAAiB,OAAA;EAclF,WAAA,CAAY,WAAA,UAAqB,aAAA,UAAuB,UAAA,EAAY,MAAA,EAAQ,QAAA,UAAkB,QAAA,YAAoB,OAAA,CAAQ,iBAAA;EAQ1H,WAAA,CAAY,WAAA,UAAqB,OAAA,WAAkB,OAAA,CAAQ,cAAA;EAI3D,WAAA,CAAY,WAAA,UAAqB,OAAA,WAAkB,OAAA;IAAU,OAAA;EAAA;EAO7D,SAAA,CAAU,WAAA,UAAqB,aAAA,UAAuB,MAAA,EAAQ,eAAA,GAAkB,OAAA,CAAQ,kBAAA;EAQxF,SAAA,CAAU,WAAA,UAAqB,aAAA,UAAuB,MAAA,EAAQ,eAAA,GAAkB,OAAA,CAAQ,kBAAA;EAQxF,YAAA,CAAa,WAAA,UAAqB,aAAA,UAAuB,MAAA,EAAQ,kBAAA,GAAqB,OAAA,CAAQ,kBAAA;EAQ9F,YAAA,CAAa,WAAA,UAAqB,aAAA,UAAuB,MAAA,EAAQ,kBAAA,GAAqB,OAAA,CAAQ,kBAAA;EAQ9F,WAAA,CAAY,WAAA,UAAqB,aAAA,UAAuB,MAAA,EAAQ,iBAAA,GAAoB,OAAA,CAAQ,kBAAA;EAQ5F,eAAA,CAAgB,WAAA,UAAqB,aAAA,UAAuB,MAAA,EAAQ,qBAAA,GAAwB,OAAA,CAAQ,kBAAA;EAQpG,YAAA,CAAa,WAAA,UAAqB,aAAA,UAAuB,MAAA,EAAQ,kBAAA,GAAqB,OAAA,CAAQ,kBAAA;EAQ9F,UAAA,CAAW,WAAA,UAAqB,aAAA,UAAuB,SAAA,WAAoB,OAAA;IAAU,OAAA;EAAA;EAAA,OAUpF,gBAAA,CAAiB,KAAA;EAIxB,sBAAA,CAAA,GAA0B,CAAA,CAAE,OAAA;EAtKe;;;EA6K3C,mBAAA,CAAA,GAAuB,eAAA;EAjKJ;;;EAuLnB,WAAA,CAAA,GAAe,gBAAA;AAAA"}
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { t as PlatformProvider } from "../base-Bw7e52V8.mjs";
|
|
2
|
+
import { t as SocialError } from "../errors-Cm6LeKf7.mjs";
|
|
3
|
+
import { a as parseGraphError, s as META_GRAPH_BASE } from "../meta-D3vcJU1c.mjs";
|
|
4
|
+
import { t as httpRequest } from "../http-DpcLSR1M.mjs";
|
|
5
|
+
import { t as WhatsAppCredentialsSchema } from "../whatsapp-CFp7ryR4.mjs";
|
|
6
|
+
|
|
7
|
+
//#region src/providers/whatsapp/index.ts
|
|
8
|
+
/**
|
|
9
|
+
* WhatsApp Platform Provider
|
|
10
|
+
* ==========================
|
|
11
|
+
* WhatsApp Business Cloud API integration via Meta Graph API.
|
|
12
|
+
*
|
|
13
|
+
* Uses token-based auth (permanent System User Token) — NOT OAuth.
|
|
14
|
+
* Users paste their access token, business account ID, and phone number ID.
|
|
15
|
+
*
|
|
16
|
+
* Key endpoints (Meta Graph API v25.0):
|
|
17
|
+
* - GET /{businessAccountId}/phone_numbers — list phone numbers
|
|
18
|
+
* - GET /{businessAccountId}/message_templates — list templates
|
|
19
|
+
* - POST /{phoneNumberId}/messages — send a message
|
|
20
|
+
*/
|
|
21
|
+
const GRAPH_API_BASE = META_GRAPH_BASE;
|
|
22
|
+
/**
|
|
23
|
+
* Human-friendly explanations for common WhatsApp Cloud API error codes.
|
|
24
|
+
* @see https://developers.facebook.com/docs/whatsapp/cloud-api/support/error-messages
|
|
25
|
+
*/
|
|
26
|
+
const ERROR_HINTS = {
|
|
27
|
+
0: "Auth exception — check that your access token is valid and has the required permissions.",
|
|
28
|
+
2: "Temporary issue — retry the request. If it persists, check your app configuration.",
|
|
29
|
+
4: "API rate limit hit — too many calls. Wait and retry.",
|
|
30
|
+
10: "Permission denied — your app does not have permission for this action. Check app permissions in Meta Developer Dashboard.",
|
|
31
|
+
100: "Invalid parameter — check the request payload (phone number format, template name, etc.).",
|
|
32
|
+
190: "Access token has expired or is invalid. Regenerate your System User Token in Business Settings → System Users.",
|
|
33
|
+
200: "Permissions error — your token lacks the required permission. Go to Business Settings → System Users → Generate New Token → check whatsapp_business_messaging, whatsapp_business_management, and whatsapp_business_manage_events. Also ensure the System User is assigned to your WABA with Full Control.",
|
|
34
|
+
131e3: "Something went wrong — retry the request.",
|
|
35
|
+
131005: "Permission denied — your access token lacks the required WhatsApp permission.",
|
|
36
|
+
131008: "Required parameter is missing from the request.",
|
|
37
|
+
131009: "Invalid parameter value — check phone number format (country code + number, no + or spaces).",
|
|
38
|
+
131016: "Service temporarily unavailable — retry in a few minutes.",
|
|
39
|
+
131021: "Recipient rate limit — too many messages to this number. Wait before retrying.",
|
|
40
|
+
131026: "Message undeliverable — the recipient may have blocked you or the number is invalid.",
|
|
41
|
+
131047: "Re-engagement message — more than 24 hours since last reply. Send a template message instead.",
|
|
42
|
+
131051: "Unsupported message type for this recipient.",
|
|
43
|
+
132e3: "Template parameter count mismatch — your template expects different parameters.",
|
|
44
|
+
132001: "Template does not exist or is not approved.",
|
|
45
|
+
132005: "Template hydration failed — parameter values don't match the expected format.",
|
|
46
|
+
132007: "Template format issue — check language code and component parameters.",
|
|
47
|
+
132012: "Template paused — this template has been paused due to quality issues.",
|
|
48
|
+
132015: "Template paused — too many messages blocked/reported.",
|
|
49
|
+
133e3: "Recipient phone number is not valid.",
|
|
50
|
+
133004: "Server is temporarily unavailable — retry later.",
|
|
51
|
+
133005: "Recipient phone number is not on WhatsApp.",
|
|
52
|
+
133010: "Recipient phone number is not registered on WhatsApp, or the number format is incorrect. Use country code + number without + or spaces (e.g. 8801712345678).",
|
|
53
|
+
133015: "Sender phone number has been deregistered. Re-register it in the WhatsApp Manager.",
|
|
54
|
+
135e3: "Generic send error — retry the request.",
|
|
55
|
+
80007: "Rate limit exceeded — too many messages sent. Wait and retry."
|
|
56
|
+
};
|
|
57
|
+
var WhatsAppProvider = class extends PlatformProvider {
|
|
58
|
+
constructor(config = {}) {
|
|
59
|
+
super(config);
|
|
60
|
+
this.name = "whatsapp";
|
|
61
|
+
this.displayName = "WhatsApp";
|
|
62
|
+
this.authType = "token";
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Shared fetch helper for Meta Graph API. Delegates to `httpRequest` for
|
|
66
|
+
* timeout, retry on 429 / 5xx, and Retry-After honoring.
|
|
67
|
+
* @private
|
|
68
|
+
*/
|
|
69
|
+
async _apiRequest(method, endpoint, accessToken, body = null) {
|
|
70
|
+
return (await httpRequest("whatsapp", {
|
|
71
|
+
method: method.toUpperCase(),
|
|
72
|
+
url: `${GRAPH_API_BASE}${endpoint}`,
|
|
73
|
+
bearer: accessToken,
|
|
74
|
+
json: body ?? void 0,
|
|
75
|
+
timeout: 3e4,
|
|
76
|
+
retry: { attempts: 2 },
|
|
77
|
+
parseError: (raw, status) => {
|
|
78
|
+
const graph = parseGraphError(raw);
|
|
79
|
+
if (!graph) return null;
|
|
80
|
+
const code = graph.errorCode;
|
|
81
|
+
const hint = typeof code === "number" || typeof code === "string" ? ERROR_HINTS[code] : void 0;
|
|
82
|
+
return {
|
|
83
|
+
message: graph.message || `Graph API error (${status})`,
|
|
84
|
+
errorCode: code,
|
|
85
|
+
hint: hint || null,
|
|
86
|
+
extras: graph.extras
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
})).data;
|
|
90
|
+
}
|
|
91
|
+
async _apiRequestFormData(endpoint, accessToken, formData) {
|
|
92
|
+
return (await httpRequest("whatsapp", {
|
|
93
|
+
method: "POST",
|
|
94
|
+
url: `${GRAPH_API_BASE}${endpoint}`,
|
|
95
|
+
bearer: accessToken,
|
|
96
|
+
form: formData,
|
|
97
|
+
timeout: 6e4,
|
|
98
|
+
retry: { attempts: 1 },
|
|
99
|
+
parseError: (raw, status) => {
|
|
100
|
+
const graph = parseGraphError(raw);
|
|
101
|
+
if (!graph) return null;
|
|
102
|
+
const code = graph.errorCode;
|
|
103
|
+
return {
|
|
104
|
+
message: graph.message || `Media upload failed (${status})`,
|
|
105
|
+
errorCode: code,
|
|
106
|
+
hint: typeof code === "number" || typeof code === "string" ? ERROR_HINTS[code] || null : null
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
})).data;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get account information (first phone number from business account)
|
|
113
|
+
*/
|
|
114
|
+
async getAccountInfo(accessToken, credentials = {}) {
|
|
115
|
+
const { businessAccountId } = credentials;
|
|
116
|
+
if (!businessAccountId) throw new SocialError("whatsapp", "Business Account ID is required", { statusCode: 400 });
|
|
117
|
+
const data = await this._apiRequest("GET", `/${businessAccountId}/phone_numbers?fields=display_phone_number,verified_name,quality_rating`, accessToken);
|
|
118
|
+
if (!data.data || data.data.length === 0) throw new SocialError("whatsapp", "No phone numbers found for this business account", { statusCode: 400 });
|
|
119
|
+
const phone = data.data[0];
|
|
120
|
+
return {
|
|
121
|
+
id: phone.id,
|
|
122
|
+
name: phone.verified_name || phone.display_phone_number,
|
|
123
|
+
profileImage: null
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Test credential validity by verifying phone numbers endpoint
|
|
128
|
+
*/
|
|
129
|
+
async testCredential(credentialData) {
|
|
130
|
+
try {
|
|
131
|
+
const { accessToken, businessAccountId } = credentialData;
|
|
132
|
+
if (!accessToken || !businessAccountId) return {
|
|
133
|
+
status: "Error",
|
|
134
|
+
message: "Access Token and Business Account ID are required"
|
|
135
|
+
};
|
|
136
|
+
const accountInfo = await this.getAccountInfo(accessToken, credentialData);
|
|
137
|
+
return {
|
|
138
|
+
status: "OK",
|
|
139
|
+
message: "WhatsApp credential is valid",
|
|
140
|
+
data: {
|
|
141
|
+
channelId: accountInfo.id,
|
|
142
|
+
channelTitle: accountInfo.name
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
} catch (error) {
|
|
146
|
+
const msg = error.message || "Failed to validate WhatsApp credential";
|
|
147
|
+
if (msg.includes("nonexisting field") && msg.includes("phone_numbers")) return {
|
|
148
|
+
status: "Error",
|
|
149
|
+
message: "Wrong ID — you entered your Meta Business ID, not the WhatsApp Business Account ID (WABA ID). Find it in Business Settings → Accounts → WhatsApp Accounts."
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
status: "Error",
|
|
153
|
+
message: msg
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get phone numbers for a business account
|
|
159
|
+
*/
|
|
160
|
+
async getPhoneNumbers(accessToken, businessAccountId) {
|
|
161
|
+
return (await this._apiRequest("GET", `/${businessAccountId}/phone_numbers?fields=id,display_phone_number,verified_name,quality_rating,code_verification_status`, accessToken)).data || [];
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get message templates for a business account (lightweight, for dropdowns)
|
|
165
|
+
*/
|
|
166
|
+
async getTemplates(accessToken, businessAccountId) {
|
|
167
|
+
return (await this._apiRequest("GET", `/${businessAccountId}/message_templates?fields=id,name,language,status,category,components`, accessToken)).data || [];
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get templates with full component details (for template management)
|
|
171
|
+
*/
|
|
172
|
+
async getTemplatesDetailed(accessToken, businessAccountId) {
|
|
173
|
+
return (await this._apiRequest("GET", `/${businessAccountId}/message_templates?fields=id,name,language,status,category,components,quality_score&limit=100`, accessToken)).data || [];
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Create a new message template
|
|
177
|
+
*/
|
|
178
|
+
async createTemplate(accessToken, businessAccountId, templateData) {
|
|
179
|
+
return this._apiRequest("POST", `/${businessAccountId}/message_templates`, accessToken, templateData);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Delete a message template by name
|
|
183
|
+
*/
|
|
184
|
+
async deleteTemplate(accessToken, businessAccountId, templateName) {
|
|
185
|
+
return this._apiRequest("DELETE", `/${businessAccountId}/message_templates?name=${encodeURIComponent(templateName)}`, accessToken);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Send a WhatsApp message
|
|
189
|
+
*/
|
|
190
|
+
async sendMessage(accessToken, phoneNumberId, payload) {
|
|
191
|
+
return this._apiRequest("POST", `/${phoneNumberId}/messages`, accessToken, {
|
|
192
|
+
messaging_product: "whatsapp",
|
|
193
|
+
...payload
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
async uploadMedia(accessToken, phoneNumberId, fileBuffer, mimeType, fileName) {
|
|
197
|
+
const formData = new FormData();
|
|
198
|
+
formData.append("messaging_product", "whatsapp");
|
|
199
|
+
formData.append("type", mimeType);
|
|
200
|
+
formData.append("file", new Blob([fileBuffer], { type: mimeType }), fileName || "file");
|
|
201
|
+
return this._apiRequestFormData(`/${phoneNumberId}/media`, accessToken, formData);
|
|
202
|
+
}
|
|
203
|
+
async getMediaUrl(accessToken, mediaId) {
|
|
204
|
+
return this._apiRequest("GET", `/${mediaId}`, accessToken);
|
|
205
|
+
}
|
|
206
|
+
async deleteMedia(accessToken, mediaId) {
|
|
207
|
+
await this._apiRequest("DELETE", `/${mediaId}`, accessToken);
|
|
208
|
+
return { success: true };
|
|
209
|
+
}
|
|
210
|
+
async sendImage(accessToken, phoneNumberId, params) {
|
|
211
|
+
return this.sendMessage(accessToken, phoneNumberId, {
|
|
212
|
+
to: params.to,
|
|
213
|
+
type: "image",
|
|
214
|
+
image: {
|
|
215
|
+
link: params.imageUrl,
|
|
216
|
+
id: params.imageId,
|
|
217
|
+
caption: params.caption
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
async sendVideo(accessToken, phoneNumberId, params) {
|
|
222
|
+
return this.sendMessage(accessToken, phoneNumberId, {
|
|
223
|
+
to: params.to,
|
|
224
|
+
type: "video",
|
|
225
|
+
video: {
|
|
226
|
+
link: params.videoUrl,
|
|
227
|
+
id: params.videoId,
|
|
228
|
+
caption: params.caption
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
async sendDocument(accessToken, phoneNumberId, params) {
|
|
233
|
+
return this.sendMessage(accessToken, phoneNumberId, {
|
|
234
|
+
to: params.to,
|
|
235
|
+
type: "document",
|
|
236
|
+
document: {
|
|
237
|
+
link: params.documentUrl,
|
|
238
|
+
id: params.documentId,
|
|
239
|
+
caption: params.caption,
|
|
240
|
+
filename: params.filename
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
async sendLocation(accessToken, phoneNumberId, params) {
|
|
245
|
+
return this.sendMessage(accessToken, phoneNumberId, {
|
|
246
|
+
to: params.to,
|
|
247
|
+
type: "location",
|
|
248
|
+
location: {
|
|
249
|
+
latitude: params.latitude,
|
|
250
|
+
longitude: params.longitude,
|
|
251
|
+
name: params.name,
|
|
252
|
+
address: params.address
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
async sendContact(accessToken, phoneNumberId, params) {
|
|
257
|
+
return this.sendMessage(accessToken, phoneNumberId, {
|
|
258
|
+
to: params.to,
|
|
259
|
+
type: "contacts",
|
|
260
|
+
contacts: params.contacts
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
async sendInteractive(accessToken, phoneNumberId, params) {
|
|
264
|
+
return this.sendMessage(accessToken, phoneNumberId, {
|
|
265
|
+
to: params.to,
|
|
266
|
+
type: "interactive",
|
|
267
|
+
interactive: params.interactive
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
async sendReaction(accessToken, phoneNumberId, params) {
|
|
271
|
+
return this.sendMessage(accessToken, phoneNumberId, {
|
|
272
|
+
to: params.to,
|
|
273
|
+
type: "reaction",
|
|
274
|
+
reaction: {
|
|
275
|
+
message_id: params.messageId,
|
|
276
|
+
emoji: params.emoji
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
async markAsRead(accessToken, phoneNumberId, messageId) {
|
|
281
|
+
return this._apiRequest("POST", `/${phoneNumberId}/messages`, accessToken, {
|
|
282
|
+
messaging_product: "whatsapp",
|
|
283
|
+
status: "read",
|
|
284
|
+
message_id: messageId
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
static cleanPhoneNumber(phone) {
|
|
288
|
+
return phone.replace(/[\s\-\(\)\+]/g, "");
|
|
289
|
+
}
|
|
290
|
+
getCredentialZodSchema() {
|
|
291
|
+
return WhatsAppCredentialsSchema;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Get credential schema for UI
|
|
295
|
+
*/
|
|
296
|
+
getCredentialSchema() {
|
|
297
|
+
return [{
|
|
298
|
+
name: "accessToken",
|
|
299
|
+
displayName: "Access Token",
|
|
300
|
+
type: "password",
|
|
301
|
+
required: true,
|
|
302
|
+
description: "Permanent System User Token from Meta Business Suite"
|
|
303
|
+
}, {
|
|
304
|
+
name: "businessAccountId",
|
|
305
|
+
displayName: "WhatsApp Business Account ID (WABA ID)",
|
|
306
|
+
type: "text",
|
|
307
|
+
required: true,
|
|
308
|
+
description: "Found in Business Settings → Accounts → WhatsApp Accounts (numeric ID). This is NOT your Meta Business ID."
|
|
309
|
+
}];
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get provider metadata for frontend display
|
|
313
|
+
*/
|
|
314
|
+
getMetadata() {
|
|
315
|
+
return {
|
|
316
|
+
name: this.name,
|
|
317
|
+
displayName: this.displayName,
|
|
318
|
+
authType: this.authType,
|
|
319
|
+
icon: "whatsapp",
|
|
320
|
+
brandColor: "#25D366",
|
|
321
|
+
description: "Send messages via WhatsApp Business Cloud API",
|
|
322
|
+
scopes: [],
|
|
323
|
+
scopeDescriptions: {},
|
|
324
|
+
supportsScheduling: false,
|
|
325
|
+
supportsEnvironment: false,
|
|
326
|
+
setupGuide: [
|
|
327
|
+
{
|
|
328
|
+
step: 1,
|
|
329
|
+
title: "Create Meta Business Account",
|
|
330
|
+
description: "Go to business.facebook.com and create a business portfolio if you don't have one"
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
step: 2,
|
|
334
|
+
title: "Create Meta App",
|
|
335
|
+
description: "Go to developers.facebook.com → My Apps → Create App → enter app name and select your business portfolio"
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
step: 3,
|
|
339
|
+
title: "Select WhatsApp Use Case",
|
|
340
|
+
description: "In the \"Use cases\" step, select \"Connect with customers through WhatsApp\" → complete the Business, Requirements, and Overview steps"
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
step: 4,
|
|
344
|
+
title: "Set Up WhatsApp",
|
|
345
|
+
description: "In your app dashboard, go to WhatsApp → API Setup → follow the quickstart to add and verify a phone number"
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
step: 5,
|
|
349
|
+
title: "Create System User",
|
|
350
|
+
description: "Go to Business Settings → Users → System Users → click \"Add\". Name it (e.g. \"api-bot\") and set role to \"Admin\""
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
step: 6,
|
|
354
|
+
title: "Assign App to System User",
|
|
355
|
+
description: "Click on your system user → \"Add Assets\" → select \"Apps\" → choose your Meta App → toggle \"Full Control\" → Save"
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
step: 7,
|
|
359
|
+
title: "Generate Token",
|
|
360
|
+
description: "Click \"Generate New Token\" → select your app → in \"Assign permissions\" check all three: ✅ whatsapp_business_messaging (send/receive messages), ✅ whatsapp_business_management (phone numbers, templates), ✅ whatsapp_business_manage_events (webhook events for incoming messages & AI replies) → Generate Token"
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
step: 8,
|
|
364
|
+
title: "Copy Credentials",
|
|
365
|
+
description: "Copy the Access Token. For the WABA ID: go to Business Settings → Accounts → WhatsApp Accounts → copy the numeric ID. ⚠️ This is NOT your Meta Business ID — it's the WhatsApp-specific account ID."
|
|
366
|
+
}
|
|
367
|
+
],
|
|
368
|
+
redirectUriPattern: null,
|
|
369
|
+
credentialSchema: this.getCredentialSchema()
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
//#endregion
|
|
375
|
+
export { WhatsAppProvider };
|
|
376
|
+
//# sourceMappingURL=whatsapp.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp.mjs","names":[],"sources":["../../src/providers/whatsapp/index.ts"],"sourcesContent":["/**\n * WhatsApp Platform Provider\n * ==========================\n * WhatsApp Business Cloud API integration via Meta Graph API.\n *\n * Uses token-based auth (permanent System User Token) — NOT OAuth.\n * Users paste their access token, business account ID, and phone number ID.\n *\n * Key endpoints (Meta Graph API v25.0):\n * - GET /{businessAccountId}/phone_numbers — list phone numbers\n * - GET /{businessAccountId}/message_templates — list templates\n * - POST /{phoneNumberId}/messages — send a message\n */\n\nimport { PlatformProvider } from '../../base.js';\nimport type { CredentialField, ProviderMetadata, TestResult, ProviderConfig } from '../../base.js';\nimport { SocialError } from '../../errors.js';\nimport { WhatsAppCredentialsSchema } from '../../schemas/whatsapp.js';\nimport { httpRequest } from '../../common/http.js';\nimport { META_GRAPH_BASE } from '../../common/meta.js';\nimport { parseGraphError } from '../../common/oauth/index.js';\nimport type { z } from 'zod';\nimport type {\n WhatsAppCredentialData,\n PhoneNumber,\n MessageTemplate,\n MessagePayload,\n CreateTemplateParams,\n WhatsAppSendResult,\n UploadMediaResult,\n MediaUrlResult,\n SendImageParams,\n SendVideoParams,\n SendDocumentParams,\n SendLocationParams,\n SendContactParams,\n SendInteractiveParams,\n SendReactionParams,\n} from './types.js';\n\nexport type {\n WhatsAppCredentialData,\n PhoneNumber,\n MessageTemplate,\n MessagePayload,\n CreateTemplateParams,\n TemplateComponent,\n TemplateButton,\n TemplateStatus,\n TemplateCategory,\n WhatsAppMessageType,\n MediaMessage,\n LocationMessage,\n ContactMessage,\n TemplateMessage,\n InteractiveMessage,\n ReactionMessage,\n WhatsAppSendResult,\n UploadMediaResult,\n MediaUrlResult,\n SendImageParams,\n SendVideoParams,\n SendDocumentParams,\n SendLocationParams,\n SendContactParams,\n SendInteractiveParams,\n SendReactionParams,\n} from './types.js';\n\nconst GRAPH_API_BASE = META_GRAPH_BASE;\n\n/**\n * Human-friendly explanations for common WhatsApp Cloud API error codes.\n * @see https://developers.facebook.com/docs/whatsapp/cloud-api/support/error-messages\n */\nconst ERROR_HINTS: Record<number, string> = {\n 0: 'Auth exception — check that your access token is valid and has the required permissions.',\n 2: 'Temporary issue — retry the request. If it persists, check your app configuration.',\n 4: 'API rate limit hit — too many calls. Wait and retry.',\n 10: 'Permission denied — your app does not have permission for this action. Check app permissions in Meta Developer Dashboard.',\n 100: 'Invalid parameter — check the request payload (phone number format, template name, etc.).',\n 190: 'Access token has expired or is invalid. Regenerate your System User Token in Business Settings → System Users.',\n 200: 'Permissions error — your token lacks the required permission. Go to Business Settings → System Users → Generate New Token → check whatsapp_business_messaging, whatsapp_business_management, and whatsapp_business_manage_events. Also ensure the System User is assigned to your WABA with Full Control.',\n 131000: 'Something went wrong — retry the request.',\n 131005: 'Permission denied — your access token lacks the required WhatsApp permission.',\n 131008: 'Required parameter is missing from the request.',\n 131009: 'Invalid parameter value — check phone number format (country code + number, no + or spaces).',\n 131016: 'Service temporarily unavailable — retry in a few minutes.',\n 131021: 'Recipient rate limit — too many messages to this number. Wait before retrying.',\n 131026: 'Message undeliverable — the recipient may have blocked you or the number is invalid.',\n 131047: 'Re-engagement message — more than 24 hours since last reply. Send a template message instead.',\n 131051: 'Unsupported message type for this recipient.',\n 132000: 'Template parameter count mismatch — your template expects different parameters.',\n 132001: 'Template does not exist or is not approved.',\n 132005: 'Template hydration failed — parameter values don\\'t match the expected format.',\n 132007: 'Template format issue — check language code and component parameters.',\n 132012: 'Template paused — this template has been paused due to quality issues.',\n 132015: 'Template paused — too many messages blocked/reported.',\n 133000: 'Recipient phone number is not valid.',\n 133004: 'Server is temporarily unavailable — retry later.',\n 133005: 'Recipient phone number is not on WhatsApp.',\n 133010: 'Recipient phone number is not registered on WhatsApp, or the number format is incorrect. Use country code + number without + or spaces (e.g. 8801712345678).',\n 133015: 'Sender phone number has been deregistered. Re-register it in the WhatsApp Manager.',\n 135000: 'Generic send error — retry the request.',\n 80007: 'Rate limit exceeded — too many messages sent. Wait and retry.',\n};\n\nexport class WhatsAppProvider extends PlatformProvider {\n constructor(config: ProviderConfig = {}) {\n super(config);\n this.name = 'whatsapp';\n this.displayName = 'WhatsApp';\n this.authType = 'token'; // No OAuth — user pastes permanent token\n }\n\n /**\n * Shared fetch helper for Meta Graph API. Delegates to `httpRequest` for\n * timeout, retry on 429 / 5xx, and Retry-After honoring.\n * @private\n */\n private async _apiRequest(method: string, endpoint: string, accessToken: string, body: Record<string, any> | null = null): Promise<any> {\n const result = await httpRequest<any>('whatsapp', {\n method: method.toUpperCase() as 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',\n url: `${GRAPH_API_BASE}${endpoint}`,\n bearer: accessToken,\n json: body ?? undefined,\n timeout: 30_000,\n retry: { attempts: 2 },\n parseError: (raw, status) => {\n const graph = parseGraphError(raw);\n if (!graph) return null;\n const code = graph.errorCode;\n const hint = (typeof code === 'number' || typeof code === 'string') ? ERROR_HINTS[code as keyof typeof ERROR_HINTS] : undefined;\n return {\n message: graph.message || `Graph API error (${status})`,\n errorCode: code,\n hint: hint || null,\n extras: graph.extras,\n };\n },\n });\n return result.data;\n }\n\n private async _apiRequestFormData(endpoint: string, accessToken: string, formData: FormData): Promise<any> {\n const result = await httpRequest<any>('whatsapp', {\n method: 'POST',\n url: `${GRAPH_API_BASE}${endpoint}`,\n bearer: accessToken,\n form: formData,\n timeout: 60_000,\n retry: { attempts: 1 },\n parseError: (raw, status) => {\n const graph = parseGraphError(raw);\n if (!graph) return null;\n const code = graph.errorCode;\n return {\n message: graph.message || `Media upload failed (${status})`,\n errorCode: code,\n hint: (typeof code === 'number' || typeof code === 'string') ? (ERROR_HINTS[code as keyof typeof ERROR_HINTS] || null) : null,\n };\n },\n });\n return result.data;\n }\n\n /**\n * Get account information (first phone number from business account)\n */\n async getAccountInfo(accessToken: string, credentials: Record<string, any> = {}): Promise<{ id: string; name: string; profileImage: null }> {\n const { businessAccountId } = credentials;\n if (!businessAccountId) {\n throw new SocialError('whatsapp', 'Business Account ID is required', { statusCode: 400 });\n }\n\n const data = await this._apiRequest(\n 'GET',\n `/${businessAccountId}/phone_numbers?fields=display_phone_number,verified_name,quality_rating`,\n accessToken,\n );\n\n if (!data.data || data.data.length === 0) {\n throw new SocialError('whatsapp', 'No phone numbers found for this business account', { statusCode: 400 });\n }\n\n const phone: PhoneNumber = data.data[0];\n return {\n id: phone.id,\n name: phone.verified_name || phone.display_phone_number,\n profileImage: null,\n };\n }\n\n /**\n * Test credential validity by verifying phone numbers endpoint\n */\n async testCredential(credentialData: Record<string, any>): Promise<TestResult> {\n try {\n const { accessToken, businessAccountId } = credentialData as WhatsAppCredentialData;\n\n if (!accessToken || !businessAccountId) {\n return {\n status: 'Error',\n message: 'Access Token and Business Account ID are required',\n };\n }\n\n const accountInfo = await this.getAccountInfo(accessToken, credentialData);\n\n return {\n status: 'OK',\n message: 'WhatsApp credential is valid',\n data: {\n channelId: accountInfo.id,\n channelTitle: accountInfo.name,\n },\n };\n } catch (error: any) {\n const msg: string = error.message || 'Failed to validate WhatsApp credential';\n // Detect wrong ID type — user likely pasted their Meta Business ID instead of WABA ID\n if (msg.includes('nonexisting field') && msg.includes('phone_numbers')) {\n return {\n status: 'Error',\n message: 'Wrong ID — you entered your Meta Business ID, not the WhatsApp Business Account ID (WABA ID). Find it in Business Settings → Accounts → WhatsApp Accounts.',\n };\n }\n return {\n status: 'Error',\n message: msg,\n };\n }\n }\n\n /**\n * Get phone numbers for a business account\n */\n async getPhoneNumbers(accessToken: string, businessAccountId: string): Promise<PhoneNumber[]> {\n const data = await this._apiRequest(\n 'GET',\n `/${businessAccountId}/phone_numbers?fields=id,display_phone_number,verified_name,quality_rating,code_verification_status`,\n accessToken,\n );\n return data.data || [];\n }\n\n /**\n * Get message templates for a business account (lightweight, for dropdowns)\n */\n async getTemplates(accessToken: string, businessAccountId: string): Promise<MessageTemplate[]> {\n const data = await this._apiRequest(\n 'GET',\n `/${businessAccountId}/message_templates?fields=id,name,language,status,category,components`,\n accessToken,\n );\n return data.data || [];\n }\n\n /**\n * Get templates with full component details (for template management)\n */\n async getTemplatesDetailed(accessToken: string, businessAccountId: string): Promise<MessageTemplate[]> {\n const data = await this._apiRequest(\n 'GET',\n `/${businessAccountId}/message_templates?fields=id,name,language,status,category,components,quality_score&limit=100`,\n accessToken,\n );\n return data.data || [];\n }\n\n /**\n * Create a new message template\n */\n async createTemplate(\n accessToken: string,\n businessAccountId: string,\n templateData: CreateTemplateParams,\n ): Promise<unknown> {\n return this._apiRequest(\n 'POST',\n `/${businessAccountId}/message_templates`,\n accessToken,\n templateData,\n );\n }\n\n /**\n * Delete a message template by name\n */\n async deleteTemplate(accessToken: string, businessAccountId: string, templateName: string): Promise<any> {\n return this._apiRequest(\n 'DELETE',\n `/${businessAccountId}/message_templates?name=${encodeURIComponent(templateName)}`,\n accessToken,\n );\n }\n\n /**\n * Send a WhatsApp message\n */\n async sendMessage(accessToken: string, phoneNumberId: string, payload: MessagePayload): Promise<any> {\n return this._apiRequest(\n 'POST',\n `/${phoneNumberId}/messages`,\n accessToken,\n {\n messaging_product: 'whatsapp',\n ...payload,\n },\n );\n }\n\n // ─── Media Management ──────────────────────────────────────────────\n\n async uploadMedia(accessToken: string, phoneNumberId: string, fileBuffer: Buffer, mimeType: string, fileName?: string): Promise<UploadMediaResult> {\n const formData = new FormData();\n formData.append('messaging_product', 'whatsapp');\n formData.append('type', mimeType);\n formData.append('file', new Blob([fileBuffer], { type: mimeType }), fileName || 'file');\n return this._apiRequestFormData(`/${phoneNumberId}/media`, accessToken, formData);\n }\n\n async getMediaUrl(accessToken: string, mediaId: string): Promise<MediaUrlResult> {\n return this._apiRequest('GET', `/${mediaId}`, accessToken);\n }\n\n async deleteMedia(accessToken: string, mediaId: string): Promise<{ success: boolean }> {\n await this._apiRequest('DELETE', `/${mediaId}`, accessToken);\n return { success: true };\n }\n\n // ─── Rich Messages (convenience wrappers) ─────────────────────────\n\n async sendImage(accessToken: string, phoneNumberId: string, params: SendImageParams): Promise<WhatsAppSendResult> {\n return this.sendMessage(accessToken, phoneNumberId, {\n to: params.to,\n type: 'image',\n image: { link: params.imageUrl, id: params.imageId, caption: params.caption },\n }) as Promise<WhatsAppSendResult>;\n }\n\n async sendVideo(accessToken: string, phoneNumberId: string, params: SendVideoParams): Promise<WhatsAppSendResult> {\n return this.sendMessage(accessToken, phoneNumberId, {\n to: params.to,\n type: 'video',\n video: { link: params.videoUrl, id: params.videoId, caption: params.caption },\n }) as Promise<WhatsAppSendResult>;\n }\n\n async sendDocument(accessToken: string, phoneNumberId: string, params: SendDocumentParams): Promise<WhatsAppSendResult> {\n return this.sendMessage(accessToken, phoneNumberId, {\n to: params.to,\n type: 'document',\n document: { link: params.documentUrl, id: params.documentId, caption: params.caption, filename: params.filename },\n }) as Promise<WhatsAppSendResult>;\n }\n\n async sendLocation(accessToken: string, phoneNumberId: string, params: SendLocationParams): Promise<WhatsAppSendResult> {\n return this.sendMessage(accessToken, phoneNumberId, {\n to: params.to,\n type: 'location',\n location: { latitude: params.latitude, longitude: params.longitude, name: params.name, address: params.address },\n }) as Promise<WhatsAppSendResult>;\n }\n\n async sendContact(accessToken: string, phoneNumberId: string, params: SendContactParams): Promise<WhatsAppSendResult> {\n return this.sendMessage(accessToken, phoneNumberId, {\n to: params.to,\n type: 'contacts',\n contacts: params.contacts,\n } as any) as Promise<WhatsAppSendResult>;\n }\n\n async sendInteractive(accessToken: string, phoneNumberId: string, params: SendInteractiveParams): Promise<WhatsAppSendResult> {\n return this.sendMessage(accessToken, phoneNumberId, {\n to: params.to,\n type: 'interactive',\n interactive: params.interactive,\n } as any) as Promise<WhatsAppSendResult>;\n }\n\n async sendReaction(accessToken: string, phoneNumberId: string, params: SendReactionParams): Promise<WhatsAppSendResult> {\n return this.sendMessage(accessToken, phoneNumberId, {\n to: params.to,\n type: 'reaction',\n reaction: { message_id: params.messageId, emoji: params.emoji },\n } as any) as Promise<WhatsAppSendResult>;\n }\n\n async markAsRead(accessToken: string, phoneNumberId: string, messageId: string): Promise<{ success: boolean }> {\n return this._apiRequest('POST', `/${phoneNumberId}/messages`, accessToken, {\n messaging_product: 'whatsapp',\n status: 'read',\n message_id: messageId,\n });\n }\n\n // ─── Phone Validation ──────────────────────────────────────────────\n\n static cleanPhoneNumber(phone: string): string {\n return phone.replace(/[\\s\\-\\(\\)\\+]/g, '');\n }\n\n getCredentialZodSchema(): z.ZodType {\n return WhatsAppCredentialsSchema;\n }\n\n /**\n * Get credential schema for UI\n */\n getCredentialSchema(): CredentialField[] {\n return [\n {\n name: 'accessToken',\n displayName: 'Access Token',\n type: 'password',\n required: true,\n description: 'Permanent System User Token from Meta Business Suite',\n },\n {\n name: 'businessAccountId',\n displayName: 'WhatsApp Business Account ID (WABA ID)',\n type: 'text',\n required: true,\n description: 'Found in Business Settings → Accounts → WhatsApp Accounts (numeric ID). This is NOT your Meta Business ID.',\n },\n ];\n }\n\n /**\n * Get provider metadata for frontend display\n */\n getMetadata(): ProviderMetadata {\n return {\n name: this.name,\n displayName: this.displayName,\n authType: this.authType,\n icon: 'whatsapp',\n brandColor: '#25D366',\n description: 'Send messages via WhatsApp Business Cloud API',\n scopes: [],\n scopeDescriptions: {},\n supportsScheduling: false,\n supportsEnvironment: false,\n setupGuide: [\n { step: 1, title: 'Create Meta Business Account', description: 'Go to business.facebook.com and create a business portfolio if you don\\'t have one' },\n { step: 2, title: 'Create Meta App', description: 'Go to developers.facebook.com → My Apps → Create App → enter app name and select your business portfolio' },\n { step: 3, title: 'Select WhatsApp Use Case', description: 'In the \"Use cases\" step, select \"Connect with customers through WhatsApp\" → complete the Business, Requirements, and Overview steps' },\n { step: 4, title: 'Set Up WhatsApp', description: 'In your app dashboard, go to WhatsApp → API Setup → follow the quickstart to add and verify a phone number' },\n { step: 5, title: 'Create System User', description: 'Go to Business Settings → Users → System Users → click \"Add\". Name it (e.g. \"api-bot\") and set role to \"Admin\"' },\n { step: 6, title: 'Assign App to System User', description: 'Click on your system user → \"Add Assets\" → select \"Apps\" → choose your Meta App → toggle \"Full Control\" → Save' },\n { step: 7, title: 'Generate Token', description: 'Click \"Generate New Token\" → select your app → in \"Assign permissions\" check all three: ✅ whatsapp_business_messaging (send/receive messages), ✅ whatsapp_business_management (phone numbers, templates), ✅ whatsapp_business_manage_events (webhook events for incoming messages & AI replies) → Generate Token' },\n { step: 8, title: 'Copy Credentials', description: 'Copy the Access Token. For the WABA ID: go to Business Settings → Accounts → WhatsApp Accounts → copy the numeric ID. ⚠️ This is NOT your Meta Business ID — it\\'s the WhatsApp-specific account ID.' },\n ],\n redirectUriPattern: null, // No OAuth redirect needed\n credentialSchema: this.getCredentialSchema(),\n };\n }\n}\n"],"mappings":"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"}
|