@happyvertical/social 0.74.8
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/AGENT.md +33 -0
- package/LICENSE +7 -0
- package/README.md +485 -0
- package/dist/index.d.ts +1114 -0
- package/dist/index.js +2695 -0
- package/dist/index.js.map +1 -0
- package/metadata.json +29 -0
- package/package.json +55 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/media.ts","../src/safety.ts","../src/types.ts","../src/adapters/bluesky.ts","../src/adapters/facebook.ts","../src/adapters/threads.ts","../src/adapters/x.ts","../src/adapters/youtube.ts","../src/index.ts"],"sourcesContent":["export interface ResolvedMediaData {\n data: Buffer;\n mimeType: string;\n}\n\nexport async function resolveMediaData(\n file: Buffer | string,\n options: {\n explicitMimeType?: string;\n fallbackMimeType?: string;\n } = {},\n): Promise<ResolvedMediaData> {\n const explicitMimeType = normalizeMimeType(options.explicitMimeType);\n\n if (Buffer.isBuffer(file)) {\n return {\n data: file,\n mimeType:\n explicitMimeType ??\n detectMimeType(file) ??\n options.fallbackMimeType ??\n 'application/octet-stream',\n };\n }\n\n const response = await fetch(file);\n if (!response.ok) {\n const snippet = await response.text();\n throw new Error(\n `Failed to fetch media from ${file}: ${response.status} ${response.statusText}${\n snippet ? ` - ${snippet.slice(0, 200)}` : ''\n }`,\n );\n }\n\n const data = Buffer.from(await response.arrayBuffer());\n\n return {\n data,\n mimeType:\n explicitMimeType ??\n normalizeMimeType(response.headers.get('content-type')) ??\n detectMimeType(data) ??\n options.fallbackMimeType ??\n 'application/octet-stream',\n };\n}\n\nexport function normalizeMimeType(value?: string | null): string | undefined {\n const mimeType = value?.split(';')[0]?.trim().toLowerCase();\n if (!mimeType || !/^[a-z0-9.+-]+\\/[a-z0-9.+-]+$/.test(mimeType)) {\n return undefined;\n }\n\n return mimeType;\n}\n\nfunction detectMimeType(data: Buffer): string | undefined {\n if (data.length >= 8 && data.subarray(0, 8).equals(PNG_SIGNATURE)) {\n return 'image/png';\n }\n\n if (data.length >= 3 && data[0] === 0xff && data[1] === 0xd8) {\n return 'image/jpeg';\n }\n\n const header = data.subarray(0, 12).toString('ascii');\n if (header.startsWith('GIF87a') || header.startsWith('GIF89a')) {\n return 'image/gif';\n }\n\n if (header.startsWith('RIFF') && header.slice(8, 12) === 'WEBP') {\n return 'image/webp';\n }\n\n if (data.length >= 12 && data.subarray(4, 8).toString('ascii') === 'ftyp') {\n const brand = data.subarray(8, 12).toString('ascii');\n return brand === 'qt ' ? 'video/quicktime' : 'video/mp4';\n }\n\n if (\n data.length >= 4 &&\n data[0] === 0x1a &&\n data[1] === 0x45 &&\n data[2] === 0xdf &&\n data[3] === 0xa3\n ) {\n return 'video/webm';\n }\n\n return undefined;\n}\n\nconst PNG_SIGNATURE = Buffer.from([\n 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,\n]);\n","import { randomUUID } from 'node:crypto';\n\nimport type {\n BaseSocialConfig,\n PostResult,\n PublishMode,\n SocialPlatformType,\n} from './types.js';\n\nexport function resolvePublishMode(\n config: Pick<BaseSocialConfig, 'publishMode'>,\n): PublishMode {\n return config.publishMode ?? 'public';\n}\n\nexport function isPublicPublishMode(mode: PublishMode): boolean {\n return mode === 'public';\n}\n\nexport function createSafetyResult(options: {\n platform: SocialPlatformType;\n mode: PublishMode;\n postType: 'text' | 'image' | 'video' | 'link';\n payload?: unknown;\n remoteId?: string;\n staged?: boolean;\n note?: string;\n metadata?: Record<string, unknown>;\n}): PostResult {\n const status = options.mode === 'dry_run' ? 'dry_run' : 'staged';\n const id =\n options.remoteId ?? `${options.platform}-${status}-${randomUUID()}`;\n\n return {\n id,\n url: '',\n status,\n metadata: {\n publishMode: options.mode,\n safety: true,\n staged: options.staged ?? status === 'staged',\n postType: options.postType,\n remoteId: options.remoteId,\n payload: options.payload,\n note: options.note,\n ...options.metadata,\n },\n };\n}\n","/**\n * Social Platform Types\n *\n * Unified interface for publishing to social media platforms.\n */\n\n/**\n * Supported social platforms\n */\nexport type SocialPlatformType =\n | 'youtube'\n | 'threads'\n | 'x'\n | 'bluesky'\n | 'facebook';\n\n/**\n * How adapters should attach links to posts.\n */\nexport type LinkBehavior = 'inline' | 'attachment' | 'reply' | 'none';\n\n/**\n * Safety mode for publish operations.\n * - dry_run: Build and validate payloads without platform API writes\n * - stage_remote: Use non-public staging endpoints where available\n * - private_or_scheduled: Create a non-public/private/scheduled platform object where available\n * - public: Publish publicly\n */\nexport type PublishMode =\n | 'dry_run'\n | 'stage_remote'\n | 'private_or_scheduled'\n | 'public';\n\n/**\n * Base configuration for social platform adapters\n */\nexport interface BaseSocialConfig {\n /**\n * Platform type\n */\n type: SocialPlatformType;\n\n /**\n * Rate limiting configuration (applies at SDK level)\n */\n rateLimit?: {\n /**\n * Maximum requests per minute\n */\n requestsPerMinute?: number;\n\n /**\n * Maximum concurrent requests\n */\n maxConcurrent?: number;\n };\n\n /**\n * Request timeout in milliseconds\n * @default 30000\n */\n timeout?: number;\n\n /**\n * Controls whether publish calls create public content.\n * Defaults to public for backward compatibility.\n */\n publishMode?: PublishMode;\n}\n\n/**\n * YouTube configuration\n */\nexport interface YouTubeConfig extends BaseSocialConfig {\n type: 'youtube';\n\n /**\n * OAuth2 client ID\n */\n clientId: string;\n\n /**\n * OAuth2 client secret\n */\n clientSecret: string;\n\n /**\n * Access token (from OAuth flow)\n */\n accessToken?: string;\n\n /**\n * Refresh token (from OAuth flow)\n */\n refreshToken?: string;\n\n /**\n * Redirect URI for OAuth\n */\n redirectUri?: string;\n}\n\n/**\n * Threads (Meta) configuration\n */\nexport interface ThreadsConfig extends BaseSocialConfig {\n type: 'threads';\n\n /**\n * Access token from Meta OAuth\n */\n accessToken: string;\n\n /**\n * Instagram/Threads user ID\n */\n userId: string;\n}\n\n/**\n * Facebook Page configuration\n */\nexport interface FacebookPageConfig extends BaseSocialConfig {\n type: 'facebook';\n\n /**\n * Page access token with pages_manage_posts permission\n */\n accessToken: string;\n\n /**\n * Facebook Page ID\n */\n pageId: string;\n\n /**\n * Optional Graph API version.\n * @default v24.0\n */\n apiVersion?: string;\n}\n\n/**\n * X (Twitter) configuration\n */\nexport interface XConfig extends BaseSocialConfig {\n type: 'x';\n\n /**\n * Authentication mode. OAuth 2.0 is used when accessSecret is omitted.\n *\n * @default 'oauth1' when accessSecret is present, otherwise 'oauth2'\n */\n authType?: 'oauth1' | 'oauth2';\n\n /**\n * API key (consumer key) for OAuth 1.0a.\n */\n apiKey?: string;\n\n /**\n * API secret (consumer secret) for OAuth 1.0a.\n */\n apiSecret?: string;\n\n /**\n * User access token.\n */\n accessToken: string;\n\n /**\n * User access token secret for OAuth 1.0a.\n */\n accessSecret?: string;\n\n /**\n * OAuth 2.0 client ID for refresh-token flows.\n */\n clientId?: string;\n\n /**\n * OAuth 2.0 client secret for confidential clients.\n */\n clientSecret?: string;\n\n /**\n * OAuth 2.0 refresh token.\n */\n refreshToken?: string;\n\n /**\n * Default handling for links.\n * @default 'inline'\n */\n linkBehavior?: LinkBehavior;\n}\n\n/**\n * Bluesky configuration\n */\nexport interface BlueskyConfig extends BaseSocialConfig {\n type: 'bluesky';\n\n /**\n * Handle or DID\n */\n identifier: string;\n\n /**\n * App password (not main password)\n */\n password: string;\n\n /**\n * PDS URL (optional, defaults to bsky.social)\n */\n pdsUrl?: string;\n}\n\n/**\n * Union of all platform configurations\n */\nexport type SocialConfig =\n | YouTubeConfig\n | ThreadsConfig\n | FacebookPageConfig\n | XConfig\n | BlueskyConfig;\n\n/**\n * Authentication result\n */\nexport interface AuthResult {\n /**\n * Access token\n */\n accessToken: string;\n\n /**\n * Refresh token (if available)\n */\n refreshToken?: string;\n\n /**\n * Token expiration time\n */\n expiresAt?: Date;\n\n /**\n * Token type (usually 'Bearer')\n */\n tokenType?: string;\n\n /**\n * Granted scopes\n */\n scopes?: string[];\n}\n\n/**\n * Video post content\n */\nexport interface VideoPost {\n /**\n * Video file buffer or URL\n */\n file: Buffer | string;\n\n /**\n * MIME type for the video file. Detected from URLs and common buffer\n * signatures when omitted.\n */\n mimeType?: string;\n\n /**\n * Video title (YouTube, some platforms)\n */\n title?: string;\n\n /**\n * Post description/caption\n */\n description?: string;\n\n /**\n * Custom thumbnail image\n */\n thumbnail?: Buffer | string;\n\n /**\n * MIME type for the thumbnail image. Detected from URLs and common buffer\n * signatures when omitted.\n */\n thumbnailMimeType?: string;\n\n /**\n * Hashtags to include\n */\n tags?: string[];\n\n /**\n * Link URL to include (e.g., article link)\n */\n linkUrl?: string;\n\n /**\n * Visibility setting\n * @default 'public'\n */\n visibility?: 'public' | 'unlisted' | 'private';\n\n /**\n * Scheduled publish time (if supported)\n */\n scheduledAt?: Date;\n\n /**\n * Category ID (YouTube)\n */\n categoryId?: string;\n\n /**\n * Whether this is a Short (YouTube)\n */\n isShort?: boolean;\n\n /**\n * Override the adapter/account default link behavior for this post.\n */\n linkBehavior?: LinkBehavior;\n}\n\n/**\n * Image post content\n */\nexport interface ImagePost {\n /**\n * Image file buffer or URL\n */\n file: Buffer | string;\n\n /**\n * MIME type for the image file. Detected from URLs and common buffer\n * signatures when omitted.\n */\n mimeType?: string;\n\n /**\n * Alt text for accessibility\n */\n altText?: string;\n\n /**\n * Post description/caption\n */\n description?: string;\n\n /**\n * Hashtags to include\n */\n tags?: string[];\n\n /**\n * Link URL to include\n */\n linkUrl?: string;\n\n /**\n * Scheduled publish time\n */\n scheduledAt?: Date;\n\n /**\n * Override the adapter/account default link behavior for this post.\n */\n linkBehavior?: LinkBehavior;\n}\n\n/**\n * Text post content\n */\nexport interface TextPost {\n /**\n * Post text content\n */\n text: string;\n\n /**\n * Hashtags to include\n */\n tags?: string[];\n\n /**\n * Link URL to include\n */\n linkUrl?: string;\n\n /**\n * Scheduled publish time\n */\n scheduledAt?: Date;\n\n /**\n * Reply to post ID (for threads/replies)\n */\n replyTo?: string;\n\n /**\n * Override the adapter/account default link behavior for this post.\n */\n linkBehavior?: LinkBehavior;\n}\n\n/**\n * Link post content\n */\nexport interface LinkPost {\n /**\n * URL to share\n */\n url: string;\n\n /**\n * Post text/caption\n */\n text?: string;\n\n /**\n * Link title for platforms that support link cards\n */\n title?: string;\n\n /**\n * Link description for platforms that support link cards\n */\n description?: string;\n\n /**\n * Hashtags to include\n */\n tags?: string[];\n\n /**\n * Scheduled publish time\n */\n scheduledAt?: Date;\n\n /**\n * Override the adapter/account default link behavior for this post.\n */\n linkBehavior?: LinkBehavior;\n}\n\n/**\n * Result from publishing a post\n */\nexport interface PostResult {\n /**\n * Platform-specific post ID\n */\n id: string;\n\n /**\n * Public URL of the post\n */\n url: string;\n\n /**\n * Publication status\n */\n status: 'published' | 'scheduled' | 'processing' | 'staged' | 'dry_run';\n\n /**\n * When the post was/will be published\n */\n publishedAt?: Date;\n\n /**\n * Scheduled time (if scheduled)\n */\n scheduledAt?: Date;\n\n /**\n * Platform-specific metadata\n */\n metadata?: Record<string, any>;\n}\n\n/**\n * Retrieved post data\n */\nexport interface Post {\n /**\n * Platform-specific post ID\n */\n id: string;\n\n /**\n * Public URL of the post\n */\n url: string;\n\n /**\n * Post type\n */\n type: 'video' | 'image' | 'text' | 'link';\n\n /**\n * Post title (if applicable)\n */\n title?: string;\n\n /**\n * Post description/caption\n */\n description?: string;\n\n /**\n * When the post was published\n */\n publishedAt: Date;\n\n /**\n * Current visibility\n */\n visibility: 'public' | 'unlisted' | 'private';\n\n /**\n * Basic analytics\n */\n analytics?: PostAnalytics;\n}\n\n/**\n * Post analytics\n */\nexport interface PostAnalytics {\n /**\n * View/impression count\n */\n views?: number;\n\n /**\n * Impression count when the platform distinguishes it from views\n */\n impressions?: number;\n\n /**\n * Like/favorite count\n */\n likes?: number;\n\n /**\n * Comment count\n */\n comments?: number;\n\n /**\n * Share/repost count\n */\n shares?: number;\n\n /**\n * Click count (for links)\n */\n clicks?: number;\n\n /**\n * Raw platform analytics payload for debugging and future reporting\n */\n raw?: unknown;\n\n /**\n * When analytics were last updated\n */\n lastUpdated?: Date;\n}\n\n/**\n * Platform capabilities\n */\nexport interface PlatformCapabilities {\n /**\n * Supports video posts\n */\n video: boolean;\n\n /**\n * Supports image posts\n */\n image: boolean;\n\n /**\n * Supports text-only posts\n */\n text: boolean;\n\n /**\n * Supports first-class link posts or link attachments\n */\n link: boolean;\n\n /**\n * Supports native link attachments/cards instead of plain inline URLs\n */\n linkAttachment?: boolean;\n\n /**\n * Supports scheduled posting\n */\n scheduling: boolean;\n\n /**\n * Supports analytics retrieval\n */\n analytics: boolean;\n\n /**\n * Analytics can include raw platform payloads\n */\n rawAnalytics?: boolean;\n\n /**\n * Safety modes supported by this adapter.\n */\n publishModes?: PublishMode[];\n\n /**\n * Supports a non-public remote staging step before final publish.\n */\n staging?: boolean;\n\n /**\n * Supports creating private, unpublished, or scheduled platform content.\n */\n privatePublishing?: boolean;\n\n /**\n * Media publishing requires a publicly accessible URL, not a Buffer upload\n */\n requiresPublicMediaUrl?: boolean;\n\n /**\n * Maximum video duration in seconds\n */\n maxVideoLength: number;\n\n /**\n * Maximum video file size in bytes\n */\n maxVideoSize: number;\n\n /**\n * Supported video formats\n */\n supportedVideoFormats: string[];\n\n /**\n * Supported aspect ratios\n */\n aspectRatios: string[];\n\n /**\n * Maximum text length\n */\n maxTextLength: number;\n\n /**\n * Maximum number of hashtags\n */\n maxHashtags?: number;\n\n /**\n * Supported high-level post types\n */\n supportedPostTypes?: Array<'text' | 'image' | 'video' | 'link'>;\n}\n\n/**\n * Social platform interface\n */\nexport interface SocialPlatform {\n /**\n * Platform type\n */\n readonly platform: SocialPlatformType;\n\n /**\n * Authenticate or refresh authentication\n */\n authenticate(credentials?: Record<string, string>): Promise<AuthResult>;\n\n /**\n * Refresh expired token\n */\n refreshToken(refreshToken: string): Promise<AuthResult>;\n\n /**\n * Publish a video post\n */\n publishVideo(video: VideoPost): Promise<PostResult>;\n\n /**\n * Publish an image post\n */\n publishImage(image: ImagePost): Promise<PostResult>;\n\n /**\n * Publish a text post\n */\n publishText(text: TextPost): Promise<PostResult>;\n\n /**\n * Publish a link post\n */\n publishLink(link: LinkPost): Promise<PostResult>;\n\n /**\n * Get a post by ID\n */\n getPost(postId: string): Promise<Post>;\n\n /**\n * Delete a post\n */\n deletePost(postId: string): Promise<void>;\n\n /**\n * Get analytics for a post\n */\n getAnalytics(postId: string): Promise<PostAnalytics>;\n\n /**\n * Get platform capabilities\n */\n getCapabilities(): PlatformCapabilities;\n}\n\n/**\n * OAuth authorization options\n */\nexport interface AuthorizationOptions {\n /**\n * OAuth scopes to request\n */\n scopes?: string[];\n\n /**\n * State parameter for CSRF protection\n */\n state?: string;\n\n /**\n * Code verifier for PKCE\n */\n codeVerifier?: string;\n\n /**\n * Redirect URI\n */\n redirectUri?: string;\n}\n\n/**\n * OAuth authorization result\n */\nexport interface AuthorizationResult {\n /**\n * Authorization URL to redirect user to\n */\n url: string;\n\n /**\n * State parameter (for verification)\n */\n state: string;\n\n /**\n * Code verifier (for PKCE, store securely)\n */\n codeVerifier?: string;\n}\n\n/**\n * OAuth code exchange parameters\n */\nexport interface CodeExchangeParams {\n /**\n * Authorization code from callback\n */\n code: string;\n\n /**\n * State parameter (for verification)\n */\n state?: string;\n\n /**\n * Code verifier (for PKCE)\n */\n codeVerifier?: string;\n\n /**\n * Redirect URI (must match authorization request)\n */\n redirectUri?: string;\n}\n\n/**\n * Error thrown by social platform operations\n */\nexport class SocialError extends Error {\n constructor(\n message: string,\n public code: string,\n public platform?: SocialPlatformType,\n public statusCode?: number,\n ) {\n super(message);\n this.name = 'SocialError';\n }\n}\n\n/**\n * Rate limit error\n */\nexport class SocialRateLimitError extends SocialError {\n constructor(\n platform: SocialPlatformType,\n public retryAfter?: number,\n ) {\n super(\n `Rate limit exceeded${retryAfter ? `, retry after ${retryAfter}s` : ''}`,\n 'RATE_LIMIT',\n platform,\n 429,\n );\n this.name = 'SocialRateLimitError';\n }\n}\n\n/**\n * Authentication error\n */\nexport class SocialAuthError extends SocialError {\n constructor(platform: SocialPlatformType, message = 'Authentication failed') {\n super(message, 'AUTH_ERROR', platform, 401);\n this.name = 'SocialAuthError';\n }\n}\n","/**\n * Bluesky Adapter\n *\n * Implements SocialPlatform interface for Bluesky (AT Protocol) publishing.\n */\n\nimport { resolveMediaData } from '../media.js';\nimport {\n createSafetyResult,\n isPublicPublishMode,\n resolvePublishMode,\n} from '../safety.js';\nimport type {\n AuthResult,\n BlueskyConfig,\n ImagePost,\n LinkPost,\n PlatformCapabilities,\n Post,\n PostAnalytics,\n PostResult,\n SocialPlatform,\n TextPost,\n VideoPost,\n} from '../types.js';\nimport {\n SocialAuthError,\n SocialError,\n SocialRateLimitError,\n} from '../types.js';\n\nconst DEFAULT_PDS = 'https://bsky.social';\n\ntype BlueskyPostRecord = Record<string, unknown> & {\n $type: 'app.bsky.feed.post';\n text: string;\n createdAt: string;\n};\n\ninterface BlueskyReplyRef {\n root: {\n uri: string;\n cid: string;\n };\n parent: {\n uri: string;\n cid: string;\n };\n}\n\ninterface BlueskyApiError {\n message?: string;\n error?: string;\n}\n\n/**\n * Bluesky adapter for publishing via AT Protocol\n *\n * @example\n * ```typescript\n * const bluesky = new BlueskyAdapter({\n * type: 'bluesky',\n * identifier: 'myhandle.bsky.social',\n * password: 'app-password', // Use app password, not main password\n * });\n *\n * await bluesky.authenticate();\n *\n * const result = await bluesky.publishText({\n * text: 'Breaking news from Bentley!',\n * linkUrl: 'https://example.com/article',\n * });\n * ```\n */\nexport class BlueskyAdapter implements SocialPlatform {\n readonly platform = 'bluesky' as const;\n private config: BlueskyConfig;\n private session?: {\n did: string;\n handle: string;\n accessJwt: string;\n refreshJwt: string;\n };\n\n constructor(config: BlueskyConfig) {\n this.config = config;\n }\n\n private get pdsUrl(): string {\n return this.config.pdsUrl ?? DEFAULT_PDS;\n }\n\n async authenticate(): Promise<AuthResult> {\n const response = await fetch(\n `${this.pdsUrl}/xrpc/com.atproto.server.createSession`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n identifier: this.config.identifier,\n password: this.config.password,\n }),\n },\n );\n\n if (!response.ok) {\n const error = await response.json();\n throw new SocialAuthError(\n 'bluesky',\n error.message ?? 'Authentication failed',\n );\n }\n\n const data = await response.json();\n\n this.session = {\n did: data.did,\n handle: data.handle,\n accessJwt: data.accessJwt,\n refreshJwt: data.refreshJwt,\n };\n\n return {\n accessToken: data.accessJwt,\n refreshToken: data.refreshJwt,\n };\n }\n\n async refreshToken(refreshJwt: string): Promise<AuthResult> {\n const response = await fetch(\n `${this.pdsUrl}/xrpc/com.atproto.server.refreshSession`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${refreshJwt}`,\n },\n },\n );\n\n if (!response.ok) {\n throw new SocialAuthError('bluesky', 'Session refresh failed');\n }\n\n const data = await response.json();\n\n this.session = {\n did: data.did,\n handle: data.handle,\n accessJwt: data.accessJwt,\n refreshJwt: data.refreshJwt,\n };\n\n return {\n accessToken: data.accessJwt,\n refreshToken: data.refreshJwt,\n };\n }\n\n async publishVideo(_video: VideoPost): Promise<PostResult> {\n throw new SocialError(\n 'Bluesky video publishing is not supported yet',\n 'NOT_SUPPORTED',\n 'bluesky',\n );\n }\n\n async publishImage(image: ImagePost): Promise<PostResult> {\n const publishMode = resolvePublishMode(this.config);\n const text = this.buildPostText(\n image.description,\n image.tags,\n image.linkUrl,\n );\n\n if (publishMode === 'dry_run') {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'image',\n payload: {\n text,\n altText: image.altText,\n linkUrl: image.linkUrl,\n },\n note: 'Bluesky dry run: blob was not uploaded and no post was created.',\n });\n }\n\n if (!this.session) {\n await this.authenticate();\n }\n\n // Upload blob first.\n const imageData = await resolveMediaData(image.file, {\n explicitMimeType: image.mimeType,\n fallbackMimeType: 'image/png',\n });\n\n const blobResponse = await fetch(\n `${this.pdsUrl}/xrpc/com.atproto.repo.uploadBlob`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.session?.accessJwt}`,\n 'Content-Type': imageData.mimeType,\n },\n body: new Uint8Array(imageData.data),\n },\n );\n\n if (!blobResponse.ok) {\n await this.handleError(blobResponse);\n }\n\n const blobData = await blobResponse.json();\n\n if (!isPublicPublishMode(publishMode)) {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'image',\n payload: {\n text,\n altText: image.altText,\n blob: blobData.blob,\n linkUrl: image.linkUrl,\n mimeType: imageData.mimeType,\n },\n remoteId: blobData.blob?.ref?.$link ?? blobData.blob?.cid,\n staged: true,\n note: 'Bluesky blob uploaded but no post record was created.',\n });\n }\n\n // Create post with image embed\n const record = {\n $type: 'app.bsky.feed.post',\n text,\n createdAt: new Date().toISOString(),\n embed: {\n $type: 'app.bsky.embed.images',\n images: [\n {\n alt: image.altText ?? '',\n image: blobData.blob,\n },\n ],\n },\n };\n\n return this.createPost(record);\n }\n\n async publishText(text: TextPost): Promise<PostResult> {\n const publishMode = resolvePublishMode(this.config);\n const postText = this.buildPostText(text.text, text.tags);\n\n const record: BlueskyPostRecord = {\n $type: 'app.bsky.feed.post',\n text: postText,\n createdAt: new Date().toISOString(),\n };\n\n // Add link card if URL provided\n if (text.linkUrl) {\n record.embed = {\n $type: 'app.bsky.embed.external',\n external: {\n uri: text.linkUrl,\n title: '', // Would need to fetch\n description: '',\n },\n };\n\n // Also add link facets for proper link rendering\n const urlStart = postText.indexOf(text.linkUrl);\n if (urlStart >= 0) {\n record.facets = [\n {\n index: {\n byteStart: urlStart,\n byteEnd: urlStart + text.linkUrl.length,\n },\n features: [\n {\n $type: 'app.bsky.richtext.facet#link',\n uri: text.linkUrl,\n },\n ],\n },\n ];\n }\n }\n\n if (!isPublicPublishMode(publishMode)) {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'text',\n payload: record,\n metadata: text.replyTo ? { replyTo: text.replyTo } : undefined,\n note:\n publishMode === 'dry_run'\n ? 'Bluesky dry run: no post record was created.'\n : 'Bluesky has no non-public text staging endpoint; no post record was created.',\n });\n }\n\n if (!this.session) {\n await this.authenticate();\n }\n\n // Handle reply\n if (text.replyTo) {\n record.reply = await this.getReplyRef(text.replyTo);\n }\n\n return this.createPost(record);\n }\n\n async publishLink(link: LinkPost): Promise<PostResult> {\n const publishMode = resolvePublishMode(this.config);\n const postText = this.buildPostText(\n link.text ?? link.title ?? link.description ?? link.url,\n link.tags,\n );\n\n const record: BlueskyPostRecord = {\n $type: 'app.bsky.feed.post',\n text: postText,\n createdAt: new Date().toISOString(),\n embed: {\n $type: 'app.bsky.embed.external',\n external: {\n uri: link.url,\n title: link.title ?? link.url,\n description: link.description ?? '',\n },\n },\n };\n\n if (!isPublicPublishMode(publishMode)) {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'link',\n payload: record,\n note:\n publishMode === 'dry_run'\n ? 'Bluesky dry run: no post record was created.'\n : 'Bluesky has no non-public link staging endpoint; no post record was created.',\n });\n }\n\n if (!this.session) {\n await this.authenticate();\n }\n\n return this.createPost(record);\n }\n\n private async createPost(record: unknown): Promise<PostResult> {\n const response = await fetch(\n `${this.pdsUrl}/xrpc/com.atproto.repo.createRecord`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.session?.accessJwt}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n repo: this.session?.did,\n collection: 'app.bsky.feed.post',\n record,\n }),\n },\n );\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n const data = await response.json();\n\n // Build AT URI for the post\n const postId = data.uri.split('/').pop();\n\n return {\n id: data.uri,\n url: `https://bsky.app/profile/${this.session?.handle}/post/${postId}`,\n status: 'published',\n publishedAt: new Date(),\n metadata: { publishMode: resolvePublishMode(this.config) },\n };\n }\n\n async getPost(postId: string): Promise<Post> {\n if (!this.session) {\n await this.authenticate();\n }\n\n // postId should be the AT URI or rkey\n const uri = postId.startsWith('at://')\n ? postId\n : `at://${this.session?.did}/app.bsky.feed.post/${postId}`;\n\n const response = await fetch(\n `${this.pdsUrl}/xrpc/app.bsky.feed.getPostThread?uri=${encodeURIComponent(uri)}&depth=0`,\n {\n headers: { Authorization: `Bearer ${this.session?.accessJwt}` },\n },\n );\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n const data = await response.json();\n const post = data.thread.post;\n\n return {\n id: post.uri,\n url: `https://bsky.app/profile/${post.author.handle}/post/${post.uri.split('/').pop()}`,\n type: 'text',\n description: post.record.text,\n publishedAt: new Date(post.record.createdAt),\n visibility: 'public',\n analytics: {\n likes: post.likeCount,\n shares: post.repostCount,\n comments: post.replyCount,\n lastUpdated: new Date(),\n raw: {\n likeCount: post.likeCount,\n repostCount: post.repostCount,\n replyCount: post.replyCount,\n quoteCount: post.quoteCount,\n },\n },\n };\n }\n\n async deletePost(postId: string): Promise<void> {\n if (!this.session) {\n await this.authenticate();\n }\n\n const rkey = postId.includes('/') ? postId.split('/').pop() : postId;\n\n const response = await fetch(\n `${this.pdsUrl}/xrpc/com.atproto.repo.deleteRecord`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.session?.accessJwt}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n repo: this.session?.did,\n collection: 'app.bsky.feed.post',\n rkey,\n }),\n },\n );\n\n if (!response.ok) {\n await this.handleError(response);\n }\n }\n\n async getAnalytics(postId: string): Promise<PostAnalytics> {\n const post = await this.getPost(postId);\n return post.analytics ?? {};\n }\n\n getCapabilities(): PlatformCapabilities {\n return {\n video: false, // Limited video support\n image: true,\n text: true,\n link: true,\n linkAttachment: true,\n scheduling: false,\n analytics: true,\n rawAnalytics: true,\n publishModes: ['dry_run', 'stage_remote', 'public'],\n staging: true,\n privatePublishing: false,\n maxVideoLength: 0,\n maxVideoSize: 0,\n supportedVideoFormats: [],\n aspectRatios: ['1:1', '16:9', '4:3'],\n maxTextLength: 300,\n maxHashtags: undefined, // No limit\n supportedPostTypes: ['text', 'image', 'link'],\n };\n }\n\n /**\n * Get reply reference for threading\n */\n private async getReplyRef(parentUri: string): Promise<BlueskyReplyRef> {\n const response = await fetch(\n `${this.pdsUrl}/xrpc/app.bsky.feed.getPostThread?uri=${encodeURIComponent(parentUri)}&depth=0`,\n {\n headers: { Authorization: `Bearer ${this.session?.accessJwt}` },\n },\n );\n\n if (!response.ok) {\n throw new SocialError(\n 'Failed to get parent post',\n 'NOT_FOUND',\n 'bluesky',\n );\n }\n\n const data = await response.json();\n const parent = data.thread.post;\n\n // Get root from parent's reply or use parent as root\n const root = parent.record.reply?.root ?? {\n uri: parent.uri,\n cid: parent.cid,\n };\n\n return {\n root,\n parent: {\n uri: parent.uri,\n cid: parent.cid,\n },\n };\n }\n\n /**\n * Build post text with hashtags\n */\n private buildPostText(\n text?: string,\n tags?: string[],\n linkUrl?: string,\n ): string {\n let result = text ?? '';\n\n if (linkUrl && !result.includes(linkUrl)) {\n result += `\\n\\n${linkUrl}`;\n }\n\n if (tags && tags.length > 0) {\n const hashtags = tags.map((t) => (t.startsWith('#') ? t : `#${t}`));\n result += `\\n\\n${hashtags.join(' ')}`;\n }\n\n // Truncate to 300 chars\n if (result.length > 300) {\n result = `${result.substring(0, 297)}...`;\n }\n\n return result;\n }\n\n /**\n * Handle API errors\n */\n private async handleError(response: Response): Promise<never> {\n const text = await response.text();\n let error: BlueskyApiError;\n try {\n error = JSON.parse(text) as BlueskyApiError;\n } catch {\n error = { message: text };\n }\n\n if (response.status === 401) {\n throw new SocialAuthError('bluesky', error.message ?? 'Unauthorized');\n }\n\n if (response.status === 429) {\n throw new SocialRateLimitError('bluesky');\n }\n\n throw new SocialError(\n error.message ?? 'API request failed',\n error.error ?? 'API_ERROR',\n 'bluesky',\n response.status,\n );\n }\n}\n","/**\n * Facebook Pages Adapter\n *\n * Implements SocialPlatform interface for Facebook Page publishing.\n */\n\nimport {\n createSafetyResult,\n isPublicPublishMode,\n resolvePublishMode,\n} from '../safety.js';\nimport type {\n AuthResult,\n FacebookPageConfig,\n ImagePost,\n LinkPost,\n PlatformCapabilities,\n Post,\n PostAnalytics,\n PostResult,\n PublishMode,\n SocialPlatform,\n TextPost,\n VideoPost,\n} from '../types.js';\nimport {\n SocialAuthError,\n SocialError,\n SocialRateLimitError,\n} from '../types.js';\n\ninterface FacebookInsight {\n name?: string;\n values?: Array<{ value?: unknown }>;\n}\n\ninterface FacebookApiError {\n error?: {\n code?: number | string;\n message?: string;\n };\n}\n\n/**\n * Facebook Page adapter for publishing feed posts and Page videos.\n */\nexport class FacebookPageAdapter implements SocialPlatform {\n readonly platform = 'facebook' as const;\n private config: FacebookPageConfig;\n\n constructor(config: FacebookPageConfig) {\n this.config = config;\n }\n\n private get graphUrl(): string {\n return `https://graph.facebook.com/${this.config.apiVersion ?? 'v24.0'}`;\n }\n\n async authenticate(): Promise<AuthResult> {\n const response = await fetch(\n `${this.graphUrl}/${this.config.pageId}?fields=id,name,link&access_token=${encodeURIComponent(this.config.accessToken)}`,\n );\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n return {\n accessToken: this.config.accessToken,\n };\n }\n\n async refreshToken(_refreshToken: string): Promise<AuthResult> {\n throw new SocialError(\n 'Facebook Page access tokens are refreshed through Meta OAuth',\n 'NOT_IMPLEMENTED',\n 'facebook',\n );\n }\n\n async publishText(text: TextPost): Promise<PostResult> {\n const publishMode = resolvePublishMode(this.config);\n if (publishMode === 'dry_run') {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'text',\n payload: {\n message: this.buildPostText(text.text, text.tags),\n link: text.linkUrl,\n },\n });\n }\n\n return this.createFeedPost(\n {\n message: this.buildPostText(text.text, text.tags),\n ...(text.linkUrl ? { link: text.linkUrl } : {}),\n ...this.safetyFeedFields(publishMode, text.scheduledAt),\n },\n publishMode,\n );\n }\n\n async publishLink(link: LinkPost): Promise<PostResult> {\n const publishMode = resolvePublishMode(this.config);\n const payload = {\n message: this.buildPostText(\n link.text ?? link.title ?? link.description ?? '',\n link.tags,\n ),\n link: link.url,\n ...this.safetyFeedFields(publishMode, link.scheduledAt),\n };\n\n if (publishMode === 'dry_run') {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'link',\n payload,\n });\n }\n\n return this.createFeedPost(payload, publishMode);\n }\n\n async publishImage(image: ImagePost): Promise<PostResult> {\n const publishMode = resolvePublishMode(this.config);\n if (publishMode === 'dry_run') {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'image',\n payload: {\n caption: this.buildPostText(\n image.description,\n image.tags,\n image.linkUrl,\n ),\n url: typeof image.file === 'string' ? image.file : undefined,\n link: image.linkUrl,\n },\n });\n }\n\n const form = new FormData();\n form.set('access_token', this.config.accessToken);\n form.set(\n 'caption',\n this.buildPostText(image.description, image.tags, image.linkUrl),\n );\n for (const [key, value] of Object.entries(\n this.safetyFeedFields(publishMode, image.scheduledAt),\n )) {\n if (value !== undefined) form.set(key, value);\n }\n\n if (typeof image.file === 'string') {\n form.set('url', image.file);\n } else {\n form.set('source', new Blob([new Uint8Array(image.file)]));\n }\n\n const response = await fetch(\n `${this.graphUrl}/${this.config.pageId}/photos`,\n {\n method: 'POST',\n body: form,\n },\n );\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n const data = await response.json();\n return this.toPostResult(\n data.post_id ?? data.id,\n this.safetyResultStatus(publishMode, image.scheduledAt),\n publishMode,\n );\n }\n\n async publishVideo(video: VideoPost): Promise<PostResult> {\n const publishMode = resolvePublishMode(this.config);\n if (publishMode === 'dry_run') {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'video',\n payload: {\n title: video.title,\n description: this.buildPostText(\n video.description,\n video.tags,\n video.linkUrl,\n ),\n fileUrl: typeof video.file === 'string' ? video.file : undefined,\n link: video.linkUrl,\n scheduledAt: video.scheduledAt?.toISOString(),\n },\n });\n }\n\n const form = new FormData();\n form.set('access_token', this.config.accessToken);\n form.set(\n 'description',\n this.buildPostText(video.description, video.tags, video.linkUrl),\n );\n for (const [key, value] of Object.entries(\n this.safetyFeedFields(publishMode, video.scheduledAt),\n )) {\n if (value !== undefined) form.set(key, value);\n }\n if (video.title) {\n form.set('title', video.title);\n }\n if (video.linkUrl) {\n form.set('embeddable', 'true');\n }\n\n if (typeof video.file === 'string') {\n form.set('file_url', video.file);\n } else {\n form.set('source', new Blob([new Uint8Array(video.file)]));\n }\n\n const response = await fetch(\n `${this.graphUrl}/${this.config.pageId}/videos`,\n {\n method: 'POST',\n body: form,\n },\n );\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n const data = await response.json();\n return this.toPostResult(\n data.id,\n this.safetyResultStatus(publishMode, video.scheduledAt, 'processing'),\n publishMode,\n );\n }\n\n async getPost(postId: string): Promise<Post> {\n const response = await fetch(\n `${this.graphUrl}/${postId}?fields=id,message,created_time,permalink_url,attachments{media_type},insights.metric(post_impressions,post_clicks,post_reactions_by_type_total,post_comments,post_shares)&access_token=${encodeURIComponent(this.config.accessToken)}`,\n );\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n const data = await response.json();\n const mediaType = data.attachments?.data?.[0]?.media_type;\n\n return {\n id: data.id,\n url: data.permalink_url ?? `https://www.facebook.com/${data.id}`,\n type:\n mediaType === 'video'\n ? 'video'\n : mediaType === 'photo'\n ? 'image'\n : mediaType === 'share'\n ? 'link'\n : 'text',\n description: data.message,\n publishedAt: data.created_time ? new Date(data.created_time) : new Date(),\n visibility: 'public',\n analytics: this.parseInsights(data.insights?.data ?? []),\n };\n }\n\n async deletePost(postId: string): Promise<void> {\n const response = await fetch(\n `${this.graphUrl}/${postId}?access_token=${encodeURIComponent(this.config.accessToken)}`,\n { method: 'DELETE' },\n );\n\n if (!response.ok) {\n await this.handleError(response);\n }\n }\n\n async getAnalytics(postId: string): Promise<PostAnalytics> {\n const post = await this.getPost(postId);\n return post.analytics ?? {};\n }\n\n getCapabilities(): PlatformCapabilities {\n return {\n video: true,\n image: true,\n text: true,\n link: true,\n linkAttachment: true,\n scheduling: true,\n analytics: true,\n rawAnalytics: true,\n publishModes: [\n 'dry_run',\n 'stage_remote',\n 'private_or_scheduled',\n 'public',\n ],\n staging: true,\n privatePublishing: true,\n maxVideoLength: 240 * 60,\n maxVideoSize: 10 * 1024 * 1024 * 1024,\n supportedVideoFormats: ['mp4', 'mov'],\n aspectRatios: ['16:9', '1:1', '9:16', '4:5'],\n maxTextLength: 63206,\n maxHashtags: undefined,\n supportedPostTypes: ['text', 'image', 'video', 'link'],\n };\n }\n\n private async createFeedPost(\n fields: Record<string, string | undefined>,\n publishMode: PublishMode = 'public',\n ): Promise<PostResult> {\n const body = new URLSearchParams();\n body.set('access_token', this.config.accessToken);\n\n for (const [key, value] of Object.entries(fields)) {\n if (value !== undefined && value !== '') {\n body.set(key, value);\n }\n }\n\n const response = await fetch(\n `${this.graphUrl}/${this.config.pageId}/feed`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body,\n },\n );\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n const data = await response.json();\n const status =\n fields.published === 'false' && fields.scheduled_publish_time\n ? 'scheduled'\n : fields.published === 'false'\n ? 'staged'\n : 'published';\n return this.toPostResult(data.id, status, publishMode);\n }\n\n private toPostResult(\n id: string,\n status: PostResult['status'],\n publishMode: PublishMode = 'public',\n ): PostResult {\n return {\n id,\n url: `https://www.facebook.com/${id}`,\n status,\n publishedAt: status === 'published' ? new Date() : undefined,\n metadata: {\n publishMode,\n safety: publishMode !== 'public',\n },\n };\n }\n\n private safetyFeedFields(\n publishMode: ReturnType<typeof resolvePublishMode>,\n scheduledAt?: Date,\n ): Record<string, string | undefined> {\n if (isPublicPublishMode(publishMode)) {\n return {};\n }\n\n return {\n published: 'false',\n ...(scheduledAt\n ? {\n scheduled_publish_time: Math.floor(\n scheduledAt.getTime() / 1000,\n ).toString(),\n }\n : {}),\n };\n }\n\n private safetyResultStatus(\n publishMode: ReturnType<typeof resolvePublishMode>,\n scheduledAt?: Date,\n publicStatus: PostResult['status'] = 'published',\n ): PostResult['status'] {\n if (isPublicPublishMode(publishMode)) return publicStatus;\n return scheduledAt ? 'scheduled' : 'staged';\n }\n\n private buildPostText(\n text?: string,\n tags?: string[],\n linkUrl?: string,\n ): string {\n let result = text ?? '';\n\n if (linkUrl && !result.includes(linkUrl)) {\n result += result.length > 0 ? `\\n\\n${linkUrl}` : linkUrl;\n }\n\n if (tags && tags.length > 0) {\n const hashtags = tags.map((tag) =>\n tag.startsWith('#') ? tag : `#${tag}`,\n );\n result +=\n result.length > 0 ? `\\n\\n${hashtags.join(' ')}` : hashtags.join(' ');\n }\n\n return result;\n }\n\n private parseInsights(insights: FacebookInsight[]): PostAnalytics {\n const analytics: PostAnalytics = { lastUpdated: new Date(), raw: insights };\n\n for (const insight of insights) {\n const value = insight.values?.[0]?.value;\n switch (insight.name) {\n case 'post_impressions':\n analytics.views = typeof value === 'number' ? value : undefined;\n analytics.impressions = analytics.views;\n break;\n case 'post_clicks':\n analytics.clicks = typeof value === 'number' ? value : undefined;\n break;\n case 'post_comments':\n analytics.comments = typeof value === 'number' ? value : undefined;\n break;\n case 'post_shares':\n analytics.shares = typeof value === 'number' ? value : undefined;\n break;\n case 'post_reactions_by_type_total':\n analytics.likes =\n typeof value === 'object' && value !== null\n ? this.sumPositiveReactions(value as Record<string, unknown>)\n : undefined;\n break;\n }\n }\n\n return analytics;\n }\n\n private sumPositiveReactions(reactions: Record<string, unknown>): number {\n return ['like', 'love', 'haha', 'wow'].reduce((sum, key) => {\n const count = reactions[key];\n return sum + (typeof count === 'number' ? count : 0);\n }, 0);\n }\n\n private async handleError(response: Response): Promise<never> {\n const text = await response.text();\n let error: FacebookApiError;\n try {\n error = JSON.parse(text) as FacebookApiError;\n } catch {\n error = { error: { message: text } };\n }\n\n if (response.status === 401 || error.error?.code === 190) {\n throw new SocialAuthError(\n 'facebook',\n error.error?.message ?? 'Unauthorized',\n );\n }\n\n if (response.status === 429 || error.error?.code === 4) {\n throw new SocialRateLimitError('facebook');\n }\n\n throw new SocialError(\n error.error?.message ?? 'API request failed',\n error.error?.code?.toString() ?? 'API_ERROR',\n 'facebook',\n response.status,\n );\n }\n}\n","/**\n * Threads (Meta) Adapter\n *\n * Implements SocialPlatform interface for Threads publishing.\n * Uses Meta Graph API.\n */\n\nimport {\n createSafetyResult,\n isPublicPublishMode,\n resolvePublishMode,\n} from '../safety.js';\nimport type {\n AuthResult,\n ImagePost,\n LinkPost,\n PlatformCapabilities,\n Post,\n PostAnalytics,\n PostResult,\n PublishMode,\n SocialPlatform,\n TextPost,\n ThreadsConfig,\n VideoPost,\n} from '../types.js';\nimport {\n SocialAuthError,\n SocialError,\n SocialRateLimitError,\n} from '../types.js';\n\nconst THREADS_API_URL = 'https://graph.threads.net/v1.0';\n\ninterface ThreadsInsight {\n name?: string;\n values?: Array<{ value?: unknown }>;\n}\n\ninterface ThreadsApiError {\n error?: {\n code?: number | string;\n message?: string;\n };\n}\n\n/**\n * Threads adapter for publishing via Meta Graph API\n *\n * @example\n * ```typescript\n * const threads = new ThreadsAdapter({\n * type: 'threads',\n * accessToken: 'user-access-token',\n * userId: 'threads-user-id',\n * });\n *\n * await threads.authenticate();\n *\n * const result = await threads.publishText({\n * text: 'Breaking news from Bentley!',\n * linkUrl: 'https://example.com/article',\n * });\n * ```\n */\nexport class ThreadsAdapter implements SocialPlatform {\n readonly platform = 'threads' as const;\n private config: ThreadsConfig;\n\n constructor(config: ThreadsConfig) {\n this.config = config;\n }\n\n async authenticate(): Promise<AuthResult> {\n // Verify token by getting user profile\n const response = await fetch(\n `${THREADS_API_URL}/${this.config.userId}?fields=id,username`,\n {\n headers: {\n Authorization: `Bearer ${this.config.accessToken}`,\n },\n },\n );\n\n if (!response.ok) {\n const error = await response.json();\n throw new SocialAuthError(\n 'threads',\n error.error?.message ?? 'Authentication failed',\n );\n }\n\n return {\n accessToken: this.config.accessToken,\n };\n }\n\n async refreshToken(_refreshToken: string): Promise<AuthResult> {\n // Threads uses long-lived tokens from Meta OAuth\n // Token refresh is handled through Meta's token exchange\n throw new SocialError(\n 'Use Meta OAuth token exchange for refresh',\n 'NOT_IMPLEMENTED',\n 'threads',\n );\n }\n\n async publishVideo(video: VideoPost): Promise<PostResult> {\n const publishMode = resolvePublishMode(this.config);\n const text = this.buildPostText(video.description, video.tags);\n\n const dryRunPayload = {\n media_type: 'VIDEO',\n ...(typeof video.file === 'string' ? { video_url: video.file } : {}),\n text,\n ...(video.linkUrl ? { link_attachment: video.linkUrl } : {}),\n };\n\n if (publishMode === 'dry_run') {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'video',\n payload: dryRunPayload,\n note: 'Threads dry run: media container was not created.',\n });\n }\n\n // Step 1: Create media container\n // For video, we need a URL (can't upload buffer directly)\n const videoUrl = Buffer.isBuffer(video.file)\n ? await this.uploadToTempStorage(video.file, 'video/mp4')\n : video.file;\n\n const payload = {\n media_type: 'VIDEO',\n video_url: videoUrl,\n text,\n ...(video.linkUrl ? { link_attachment: video.linkUrl } : {}),\n };\n\n const containerResponse = await fetch(\n `${THREADS_API_URL}/${this.config.userId}/threads`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.accessToken}`,\n },\n body: JSON.stringify(payload),\n },\n );\n\n if (!containerResponse.ok) {\n await this.handleError(containerResponse);\n }\n\n const containerData = await containerResponse.json();\n const containerId = containerData.id;\n\n // Step 2: Wait for container to be ready\n await this.waitForContainer(containerId);\n\n if (!isPublicPublishMode(publishMode)) {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'video',\n payload,\n remoteId: containerId,\n staged: true,\n note: 'Threads media container created but not published.',\n });\n }\n\n // Step 3: Publish the container\n return this.publishContainer(containerId, publishMode);\n }\n\n async publishImage(image: ImagePost): Promise<PostResult> {\n const publishMode = resolvePublishMode(this.config);\n const text = this.buildPostText(image.description, image.tags);\n\n const dryRunPayload = {\n media_type: 'IMAGE',\n ...(typeof image.file === 'string' ? { image_url: image.file } : {}),\n text,\n ...(image.linkUrl ? { link_attachment: image.linkUrl } : {}),\n };\n\n if (publishMode === 'dry_run') {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'image',\n payload: dryRunPayload,\n note: 'Threads dry run: media container was not created.',\n });\n }\n\n // Step 1: Create media container\n // For image, we need a URL\n const imageUrl = Buffer.isBuffer(image.file)\n ? await this.uploadToTempStorage(image.file, 'image/png')\n : image.file;\n\n const payload = {\n media_type: 'IMAGE',\n image_url: imageUrl,\n text,\n ...(image.linkUrl ? { link_attachment: image.linkUrl } : {}),\n };\n\n const containerResponse = await fetch(\n `${THREADS_API_URL}/${this.config.userId}/threads`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.accessToken}`,\n },\n body: JSON.stringify(payload),\n },\n );\n\n if (!containerResponse.ok) {\n await this.handleError(containerResponse);\n }\n\n const containerData = await containerResponse.json();\n const containerId = containerData.id;\n\n // Step 2: Wait for container to be ready\n await this.waitForContainer(containerId);\n\n if (!isPublicPublishMode(publishMode)) {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'image',\n payload,\n remoteId: containerId,\n staged: true,\n note: 'Threads media container created but not published.',\n });\n }\n\n // Step 3: Publish the container\n return this.publishContainer(containerId, publishMode);\n }\n\n async publishText(text: TextPost): Promise<PostResult> {\n const publishMode = resolvePublishMode(this.config);\n const postText = this.buildPostText(text.text, text.tags);\n\n // Step 1: Create text container\n const body: Record<string, unknown> = {\n media_type: 'TEXT',\n text: postText,\n };\n\n if (text.linkUrl) {\n body.link_attachment = text.linkUrl;\n }\n\n // Handle reply\n if (text.replyTo) {\n body.reply_to_id = text.replyTo;\n }\n\n if (publishMode === 'dry_run') {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'text',\n payload: body,\n note: 'Threads dry run: text container was not created.',\n });\n }\n\n const containerResponse = await fetch(\n `${THREADS_API_URL}/${this.config.userId}/threads`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.accessToken}`,\n },\n body: JSON.stringify(body),\n },\n );\n\n if (!containerResponse.ok) {\n await this.handleError(containerResponse);\n }\n\n const containerData = await containerResponse.json();\n const containerId = containerData.id;\n\n if (!isPublicPublishMode(publishMode)) {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'text',\n payload: body,\n remoteId: containerId,\n staged: true,\n note: 'Threads text container created but not published.',\n });\n }\n\n // Step 2: Publish the container (text doesn't need processing wait)\n return this.publishContainer(containerId, publishMode);\n }\n\n async publishLink(link: LinkPost): Promise<PostResult> {\n return this.publishText({\n text: link.text ?? link.title ?? link.description ?? link.url,\n tags: link.tags,\n linkUrl: link.url,\n scheduledAt: link.scheduledAt,\n linkBehavior: link.linkBehavior,\n });\n }\n\n /**\n * Wait for media container to finish processing\n */\n private async waitForContainer(containerId: string): Promise<void> {\n let checkCount = 0;\n const maxChecks = 60; // 5 minutes max\n\n while (checkCount < maxChecks) {\n const statusResponse = await fetch(\n `${THREADS_API_URL}/${containerId}?fields=status`,\n {\n headers: {\n Authorization: `Bearer ${this.config.accessToken}`,\n },\n },\n );\n\n if (!statusResponse.ok) {\n throw new SocialError(\n 'Failed to check container status',\n 'STATUS_CHECK_FAILED',\n 'threads',\n );\n }\n\n const statusData = await statusResponse.json();\n\n if (statusData.status === 'FINISHED') {\n return;\n }\n\n if (statusData.status === 'ERROR') {\n throw new SocialError(\n 'Media processing failed',\n 'PROCESSING_FAILED',\n 'threads',\n );\n }\n\n // IN_PROGRESS - wait and check again\n await new Promise((resolve) => setTimeout(resolve, 5000));\n checkCount++;\n }\n\n throw new SocialError(\n 'Media processing timeout',\n 'PROCESSING_TIMEOUT',\n 'threads',\n );\n }\n\n /**\n * Publish a prepared container\n */\n private async publishContainer(\n containerId: string,\n publishMode: PublishMode = 'public',\n ): Promise<PostResult> {\n const publishResponse = await fetch(\n `${THREADS_API_URL}/${this.config.userId}/threads_publish`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.config.accessToken}`,\n },\n body: JSON.stringify({\n creation_id: containerId,\n }),\n },\n );\n\n if (!publishResponse.ok) {\n await this.handleError(publishResponse);\n }\n\n const publishData = await publishResponse.json();\n\n // Get the published thread details\n const threadResponse = await fetch(\n `${THREADS_API_URL}/${publishData.id}?fields=id,permalink`,\n {\n headers: {\n Authorization: `Bearer ${this.config.accessToken}`,\n },\n },\n );\n\n const threadData = await threadResponse.json();\n\n return {\n id: publishData.id,\n url:\n threadData.permalink ??\n `https://www.threads.net/@${this.config.userId}/post/${publishData.id}`,\n status: 'published',\n publishedAt: new Date(),\n metadata: { publishMode },\n };\n }\n\n /**\n * Upload buffer to temporary storage and return URL\n * Note: In production, this would upload to a CDN or storage service\n */\n private async uploadToTempStorage(\n _buffer: Buffer,\n _mimeType: string,\n ): Promise<string> {\n // Threads requires a publicly accessible URL for media\n // This would typically upload to S3, CloudFlare R2, etc.\n throw new SocialError(\n 'Buffer upload requires external storage configuration. Provide a URL instead.',\n 'NOT_IMPLEMENTED',\n 'threads',\n );\n }\n\n async getPost(postId: string): Promise<Post> {\n const response = await fetch(\n `${THREADS_API_URL}/${postId}?fields=id,text,timestamp,media_type,permalink`,\n {\n headers: {\n Authorization: `Bearer ${this.config.accessToken}`,\n },\n },\n );\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n const data = await response.json();\n\n // Get insights separately\n let analytics: PostAnalytics = {};\n try {\n const insightsResponse = await fetch(\n `${THREADS_API_URL}/${postId}/insights?metric=views,likes,replies,reposts`,\n {\n headers: {\n Authorization: `Bearer ${this.config.accessToken}`,\n },\n },\n );\n if (insightsResponse.ok) {\n const insightsData = await insightsResponse.json();\n analytics = this.parseInsights(insightsData.data);\n }\n } catch {\n // Insights may not be available for all posts\n }\n\n return {\n id: data.id,\n url: data.permalink,\n type: this.mapMediaType(data.media_type),\n description: data.text,\n publishedAt: new Date(data.timestamp),\n visibility: 'public',\n analytics,\n };\n }\n\n async deletePost(_postId: string): Promise<void> {\n // Threads API currently doesn't support deletion via API\n // Posts must be deleted manually\n throw new SocialError(\n 'Threads does not support programmatic post deletion',\n 'NOT_SUPPORTED',\n 'threads',\n );\n }\n\n async getAnalytics(postId: string): Promise<PostAnalytics> {\n const response = await fetch(\n `${THREADS_API_URL}/${postId}/insights?metric=views,likes,replies,reposts,quotes`,\n {\n headers: {\n Authorization: `Bearer ${this.config.accessToken}`,\n },\n },\n );\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n const data = await response.json();\n return this.parseInsights(data.data);\n }\n\n getCapabilities(): PlatformCapabilities {\n return {\n video: true,\n image: true,\n text: true,\n link: true,\n linkAttachment: true,\n scheduling: false,\n analytics: true,\n rawAnalytics: true,\n requiresPublicMediaUrl: true,\n publishModes: ['dry_run', 'stage_remote', 'public'],\n staging: true,\n privatePublishing: false,\n maxVideoLength: 300, // 5 minutes\n maxVideoSize: 1024 * 1024 * 1024, // 1GB\n supportedVideoFormats: ['mp4', 'mov'],\n aspectRatios: ['1:1', '4:5', '9:16'],\n maxTextLength: 500,\n maxHashtags: undefined,\n supportedPostTypes: ['text', 'image', 'video', 'link'],\n };\n }\n\n /**\n * Build post text with hashtags and link\n */\n private buildPostText(text?: string, tags?: string[]): string {\n let result = text ?? '';\n\n if (tags && tags.length > 0) {\n const hashtags = tags.map((t) => (t.startsWith('#') ? t : `#${t}`));\n result += `\\n\\n${hashtags.join(' ')}`;\n }\n\n // Truncate to 500 chars\n if (result.length > 500) {\n result = `${result.substring(0, 497)}...`;\n }\n\n return result;\n }\n\n /**\n * Parse insights data into PostAnalytics\n */\n private parseInsights(insights: ThreadsInsight[]): PostAnalytics {\n const analytics: PostAnalytics = {};\n\n for (const insight of insights) {\n const value = insight.values?.[0]?.value;\n switch (insight.name) {\n case 'views':\n analytics.views = typeof value === 'number' ? value : undefined;\n analytics.impressions = analytics.views;\n break;\n case 'likes':\n analytics.likes = typeof value === 'number' ? value : undefined;\n break;\n case 'replies':\n analytics.comments = typeof value === 'number' ? value : undefined;\n break;\n case 'reposts':\n case 'quotes':\n if (typeof value === 'number') {\n analytics.shares = (analytics.shares ?? 0) + value;\n }\n break;\n }\n }\n\n analytics.lastUpdated = new Date();\n analytics.raw = insights;\n return analytics;\n }\n\n /**\n * Map Threads media type to our type\n */\n private mapMediaType(mediaType: string): 'video' | 'image' | 'text' {\n switch (mediaType) {\n case 'VIDEO':\n return 'video';\n case 'IMAGE':\n case 'CAROUSEL_ALBUM':\n return 'image';\n default:\n return 'text';\n }\n }\n\n /**\n * Handle API errors\n */\n private async handleError(response: Response): Promise<never> {\n const text = await response.text();\n let error: ThreadsApiError;\n try {\n error = JSON.parse(text) as ThreadsApiError;\n } catch {\n error = { error: { message: text } };\n }\n\n if (response.status === 401 || error.error?.code === 190) {\n throw new SocialAuthError(\n 'threads',\n error.error?.message ?? 'Unauthorized',\n );\n }\n\n if (response.status === 429 || error.error?.code === 4) {\n throw new SocialRateLimitError('threads');\n }\n\n throw new SocialError(\n error.error?.message ?? 'API request failed',\n error.error?.code?.toString() ?? 'API_ERROR',\n 'threads',\n response.status,\n );\n }\n}\n","/**\n * X (Twitter) Adapter\n *\n * Implements SocialPlatform interface for X (formerly Twitter) publishing.\n * Uses Twitter API v2.\n */\n\nimport { createHmac, randomUUID } from 'node:crypto';\n\nimport { createLogger } from '@happyvertical/logger';\nimport { type ResolvedMediaData, resolveMediaData } from '../media.js';\nimport {\n createSafetyResult,\n isPublicPublishMode,\n resolvePublishMode,\n} from '../safety.js';\nimport type {\n AuthResult,\n ImagePost,\n LinkBehavior,\n LinkPost,\n PlatformCapabilities,\n Post,\n PostAnalytics,\n PostResult,\n SocialPlatform,\n TextPost,\n VideoPost,\n XConfig,\n} from '../types.js';\nimport {\n SocialAuthError,\n SocialError,\n SocialRateLimitError,\n} from '../types.js';\n\nconst X_API_URL = 'https://api.twitter.com/2';\nconst X_UPLOAD_URL = 'https://upload.twitter.com/1.1';\nconst X_API_MEDIA_UPLOAD_URL = 'https://api.x.com/2/media/upload';\nconst X_API_MEDIA_METADATA_URL = 'https://api.x.com/2/media/metadata';\nconst X_OAUTH_TOKEN_URL = 'https://api.x.com/2/oauth2/token';\n\n/**\n * X (Twitter) adapter for publishing\n *\n * @example\n * ```typescript\n * const x = new XAdapter({\n * type: 'x',\n * apiKey: 'consumer-key',\n * apiSecret: 'consumer-secret',\n * accessToken: 'user-access-token',\n * accessSecret: 'user-access-secret',\n * });\n *\n * await x.authenticate();\n *\n * const result = await x.publishText({\n * text: 'Breaking news from Bentley!',\n * linkUrl: 'https://example.com/article',\n * });\n * ```\n */\nexport class XAdapter implements SocialPlatform {\n readonly platform = 'x' as const;\n private config: XConfig;\n private logger: ReturnType<typeof createLogger>;\n\n constructor(config: XConfig) {\n this.config = config;\n this.logger = createLogger({ level: 'info' });\n }\n\n /**\n * Generate OAuth 1.0a signature for request\n */\n private generateOAuthSignature(\n method: string,\n url: string,\n params: Record<string, string>,\n ): string {\n const { apiKey, apiSecret, accessSecret } = this.requireOAuth1Config();\n const oauthParams: Record<string, string> = {\n oauth_consumer_key: apiKey,\n oauth_nonce: this.generateNonce(),\n oauth_signature_method: 'HMAC-SHA1',\n oauth_timestamp: Math.floor(Date.now() / 1000).toString(),\n oauth_token: this.config.accessToken,\n oauth_version: '1.0',\n ...params,\n };\n\n // Sort and encode parameters\n const sortedParams = Object.keys(oauthParams)\n .sort()\n .map(\n (k) => `${this.percentEncode(k)}=${this.percentEncode(oauthParams[k])}`,\n )\n .join('&');\n\n // Create signature base string\n const signatureBase = [\n method.toUpperCase(),\n this.percentEncode(url),\n this.percentEncode(sortedParams),\n ].join('&');\n\n // Create signing key\n const signingKey = `${this.percentEncode(apiSecret)}&${this.percentEncode(accessSecret)}`;\n\n // Generate HMAC-SHA1 signature\n const signature = this.hmacSha1(signatureBase, signingKey);\n\n // Build Authorization header\n const authParams: Record<string, string> = {\n ...oauthParams,\n oauth_signature: signature,\n };\n\n return (\n 'OAuth ' +\n Object.keys(authParams)\n .filter((k) => k.startsWith('oauth_'))\n .sort()\n .map(\n (k) =>\n `${this.percentEncode(k)}=\"${this.percentEncode(authParams[k])}\"`,\n )\n .join(', ')\n );\n }\n\n private generateNonce(): string {\n return randomUUID().replace(/-/g, '');\n }\n\n private percentEncode(str: string): string {\n return encodeURIComponent(str).replace(\n /[!'()*]/g,\n (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n }\n\n private hmacSha1(data: string, key: string): string {\n return createHmac('sha1', key).update(data).digest('base64');\n }\n\n async authenticate(): Promise<AuthResult> {\n // Verify credentials by getting current user\n const response = await this.makeRequest(\n 'GET',\n `${X_API_URL}/users/me?user.fields=username,name`,\n );\n\n if (!response.ok) {\n throw new SocialAuthError('x', 'Invalid credentials');\n }\n\n return {\n accessToken: this.config.accessToken,\n refreshToken: this.config.refreshToken,\n };\n }\n\n async refreshToken(refreshToken: string): Promise<AuthResult> {\n if (!this.usesOAuth2()) {\n throw new SocialError(\n 'OAuth 1.0a does not support token refresh',\n 'NOT_SUPPORTED',\n 'x',\n );\n }\n if (!this.config.clientId || !this.config.clientSecret) {\n throw new SocialAuthError('x', 'Missing OAuth 2.0 client credentials');\n }\n\n const response = await fetch(X_OAUTH_TOKEN_URL, {\n method: 'POST',\n headers: {\n Authorization: this.basicAuthHeader(\n this.config.clientId,\n this.config.clientSecret,\n ),\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n }),\n });\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n const token = await response.json();\n return {\n accessToken: token.access_token,\n refreshToken: token.refresh_token,\n expiresAt:\n typeof token.expires_in === 'number'\n ? new Date(Date.now() + token.expires_in * 1000)\n : undefined,\n tokenType: token.token_type,\n scopes:\n typeof token.scope === 'string' ? token.scope.split(' ') : undefined,\n };\n }\n\n private basicAuthHeader(clientId: string, clientSecret: string): string {\n return `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString(\n 'base64',\n )}`;\n }\n\n async publishVideo(video: VideoPost): Promise<PostResult> {\n const publishMode = resolvePublishMode(this.config);\n const linkBehavior = this.resolveLinkBehavior(video.linkBehavior);\n const text = this.buildPostText(\n video.description,\n video.tags,\n video.linkUrl,\n linkBehavior,\n );\n const payload = { text, linkBehavior, tags: video.tags };\n\n if (publishMode === 'dry_run') {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'video',\n payload,\n note: 'X dry run: media was not uploaded and no post was created.',\n });\n }\n\n // Upload video first. This is safe for stage modes; media is not public\n // until attached to a post.\n const mediaId = await this.uploadMedia(video.file, 'video', video.mimeType);\n\n if (!isPublicPublishMode(publishMode)) {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'video',\n payload: { ...payload, mediaId },\n remoteId: mediaId,\n staged: true,\n note: 'X media uploaded but no post was created.',\n });\n }\n\n const response = await this.makeRequest('POST', `${X_API_URL}/tweets`, {\n text,\n media: { media_ids: [mediaId] },\n });\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n const data = await response.json();\n\n if (video.linkUrl && linkBehavior === 'reply') {\n await this.postLinkReply(data.data.id, video.linkUrl);\n }\n\n return {\n id: data.data.id,\n url: `https://x.com/i/status/${data.data.id}`,\n status: 'published',\n publishedAt: new Date(),\n metadata: { publishMode },\n };\n }\n\n async publishImage(image: ImagePost): Promise<PostResult> {\n const publishMode = resolvePublishMode(this.config);\n const linkBehavior = this.resolveLinkBehavior(image.linkBehavior);\n const text = this.buildPostText(\n image.description,\n image.tags,\n image.linkUrl,\n linkBehavior,\n );\n const payload = {\n text,\n linkBehavior,\n tags: image.tags,\n altText: image.altText,\n };\n\n if (publishMode === 'dry_run') {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'image',\n payload,\n note: 'X dry run: media was not uploaded and no post was created.',\n });\n }\n\n // Upload image first. This is safe for stage modes; media is not public\n // until attached to a post.\n const mediaId = await this.uploadMedia(image.file, 'image', image.mimeType);\n\n if (!isPublicPublishMode(publishMode)) {\n if (image.altText) {\n await this.setMediaAltText(mediaId, image.altText);\n }\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'image',\n payload: { ...payload, mediaId },\n remoteId: mediaId,\n staged: true,\n note: 'X media uploaded but no post was created.',\n });\n }\n\n const body: Record<string, unknown> = {\n text,\n media: { media_ids: [mediaId] },\n };\n\n // Add alt text if provided\n if (image.altText) {\n // Alt text is set during upload via metadata endpoint\n await this.setMediaAltText(mediaId, image.altText);\n }\n\n const response = await this.makeRequest(\n 'POST',\n `${X_API_URL}/tweets`,\n body,\n );\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n const data = await response.json();\n\n if (image.linkUrl && linkBehavior === 'reply') {\n await this.postLinkReply(data.data.id, image.linkUrl);\n }\n\n return {\n id: data.data.id,\n url: `https://x.com/i/status/${data.data.id}`,\n status: 'published',\n publishedAt: new Date(),\n metadata: { publishMode },\n };\n }\n\n async publishText(text: TextPost): Promise<PostResult> {\n const publishMode = resolvePublishMode(this.config);\n const linkBehavior = this.resolveLinkBehavior(text.linkBehavior);\n const postText = this.buildPostText(\n text.text,\n text.tags,\n text.linkUrl,\n linkBehavior,\n );\n\n const body: Record<string, unknown> = { text: postText };\n\n // Handle reply\n if (text.replyTo) {\n body.reply = { in_reply_to_tweet_id: text.replyTo };\n }\n\n if (!isPublicPublishMode(publishMode)) {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'text',\n payload: body,\n note:\n publishMode === 'dry_run'\n ? 'X dry run: no post was created.'\n : 'X has no non-public text staging endpoint; no post was created.',\n });\n }\n\n const response = await this.makeRequest(\n 'POST',\n `${X_API_URL}/tweets`,\n body,\n );\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n const data = await response.json();\n\n if (text.linkUrl && linkBehavior === 'reply' && !text.replyTo) {\n await this.postLinkReply(data.data.id, text.linkUrl);\n }\n\n return {\n id: data.data.id,\n url: `https://x.com/i/status/${data.data.id}`,\n status: 'published',\n publishedAt: new Date(),\n metadata: { publishMode },\n };\n }\n\n async publishLink(link: LinkPost): Promise<PostResult> {\n return this.publishText({\n text: link.text ?? link.title ?? link.description ?? link.url,\n tags: link.tags,\n linkUrl: link.url,\n scheduledAt: link.scheduledAt,\n linkBehavior: link.linkBehavior,\n });\n }\n\n /**\n * Upload media to Twitter\n */\n private async uploadMedia(\n file: Buffer | string,\n type: 'image' | 'video',\n mimeType?: string,\n ): Promise<string> {\n if (this.usesOAuth2()) {\n return this.uploadMediaV2(file, type, mimeType);\n }\n\n return this.uploadMediaOAuth1(file, type, mimeType);\n }\n\n private async readMediaData(\n file: Buffer | string,\n type: 'image' | 'video',\n mimeType?: string,\n ): Promise<ResolvedMediaData> {\n return resolveMediaData(file, {\n explicitMimeType: mimeType,\n fallbackMimeType: type === 'video' ? 'video/mp4' : 'image/png',\n });\n }\n\n /**\n * Upload media using X API v2 and OAuth 2.0 user context.\n */\n private async uploadMediaV2(\n file: Buffer | string,\n type: 'image' | 'video',\n mimeType?: string,\n ): Promise<string> {\n const mediaData = await this.readMediaData(file, type, mimeType);\n const mediaType = mediaData.mimeType;\n const mediaCategory = type === 'video' ? 'tweet_video' : 'tweet_image';\n\n const initResponse = await this.makeBearerUploadRequest('POST', {\n command: 'INIT',\n total_bytes: String(mediaData.data.length),\n media_type: mediaType,\n media_category: mediaCategory,\n });\n\n if (!initResponse.ok) {\n throw new SocialError('Media upload init failed', 'UPLOAD_FAILED', 'x');\n }\n\n const initData = await initResponse.json();\n const mediaId = initData.data?.id;\n if (!mediaId) {\n throw new SocialError(\n 'Media upload init did not return a media id',\n 'UPLOAD_FAILED',\n 'x',\n );\n }\n\n const chunkSize = 5 * 1024 * 1024;\n let segmentIndex = 0;\n\n for (let offset = 0; offset < mediaData.data.length; offset += chunkSize) {\n const chunk = mediaData.data.subarray(offset, offset + chunkSize);\n const formData = new FormData();\n formData.append('command', 'APPEND');\n formData.append('media_id', mediaId);\n formData.append('segment_index', segmentIndex.toString());\n formData.append(\n 'media',\n new Blob([new Uint8Array(chunk)], { type: mediaType }),\n );\n\n const appendResponse = await this.makeBearerUploadRequest(\n 'POST',\n formData,\n );\n\n if (!appendResponse.ok) {\n throw new SocialError(\n 'Media upload append failed',\n 'UPLOAD_FAILED',\n 'x',\n );\n }\n\n segmentIndex++;\n }\n\n const finalizeResponse = await this.makeBearerUploadRequest('POST', {\n command: 'FINALIZE',\n media_id: mediaId,\n });\n\n if (!finalizeResponse.ok) {\n throw new SocialError(\n 'Media upload finalize failed',\n 'UPLOAD_FAILED',\n 'x',\n );\n }\n\n const finalizeData = await finalizeResponse.json();\n\n if (type === 'video' && finalizeData.data?.processing_info) {\n await this.waitForProcessing(mediaId);\n }\n\n return mediaId;\n }\n\n /**\n * Upload media using legacy OAuth 1.0a upload endpoints.\n */\n private async uploadMediaOAuth1(\n file: Buffer | string,\n type: 'image' | 'video',\n mimeType?: string,\n ): Promise<string> {\n const mediaData = await this.readMediaData(file, type, mimeType);\n\n const mediaType = mediaData.mimeType;\n const mediaCategory = type === 'video' ? 'tweet_video' : 'tweet_image';\n\n // INIT\n const initResponse = await this.makeUploadRequest(\n 'POST',\n `${X_UPLOAD_URL}/media/upload.json`,\n {\n command: 'INIT',\n total_bytes: mediaData.data.length,\n media_type: mediaType,\n media_category: mediaCategory,\n },\n );\n\n if (!initResponse.ok) {\n throw new SocialError('Media upload init failed', 'UPLOAD_FAILED', 'x');\n }\n\n const initData = await initResponse.json();\n const mediaId = initData.media_id_string;\n\n // APPEND (chunked upload)\n const chunkSize = 5 * 1024 * 1024; // 5MB chunks\n let segmentIndex = 0;\n\n for (let offset = 0; offset < mediaData.data.length; offset += chunkSize) {\n const chunk = mediaData.data.subarray(offset, offset + chunkSize);\n\n const formData = new FormData();\n formData.append('command', 'APPEND');\n formData.append('media_id', mediaId);\n formData.append('segment_index', segmentIndex.toString());\n formData.append(\n 'media',\n new Blob([new Uint8Array(chunk)], { type: mediaType }),\n );\n\n const appendResponse = await this.makeUploadRequest(\n 'POST',\n `${X_UPLOAD_URL}/media/upload.json`,\n formData,\n true,\n );\n\n if (!appendResponse.ok) {\n throw new SocialError(\n 'Media upload append failed',\n 'UPLOAD_FAILED',\n 'x',\n );\n }\n\n segmentIndex++;\n }\n\n // FINALIZE\n const finalizeResponse = await this.makeUploadRequest(\n 'POST',\n `${X_UPLOAD_URL}/media/upload.json`,\n {\n command: 'FINALIZE',\n media_id: mediaId,\n },\n );\n\n if (!finalizeResponse.ok) {\n throw new SocialError(\n 'Media upload finalize failed',\n 'UPLOAD_FAILED',\n 'x',\n );\n }\n\n const finalizeData = await finalizeResponse.json();\n\n // Wait for processing if video\n if (type === 'video' && finalizeData.processing_info) {\n await this.waitForProcessing(mediaId);\n }\n\n return mediaId;\n }\n\n /**\n * Wait for media processing to complete\n */\n private async waitForProcessing(mediaId: string): Promise<void> {\n let checkCount = 0;\n const maxChecks = 60; // 5 minutes max\n\n while (checkCount < maxChecks) {\n const statusResponse = this.usesOAuth2()\n ? await this.makeBearerUploadRequest('GET', {\n command: 'STATUS',\n media_id: mediaId,\n })\n : await this.makeUploadRequest(\n 'GET',\n `${X_UPLOAD_URL}/media/upload.json`,\n {\n command: 'STATUS',\n media_id: mediaId,\n },\n );\n\n const statusData = await statusResponse.json();\n const processingInfo =\n statusData.processing_info ?? statusData.data?.processing_info;\n\n if (!processingInfo || processingInfo.state === 'succeeded') {\n return;\n }\n\n if (processingInfo.state === 'failed') {\n throw new SocialError(\n processingInfo.error?.message ?? 'Media processing failed',\n 'PROCESSING_FAILED',\n 'x',\n );\n }\n\n // Wait before next check\n const waitTime = (processingInfo.check_after_secs ?? 5) * 1000;\n await new Promise((resolve) => setTimeout(resolve, waitTime));\n checkCount++;\n }\n\n throw new SocialError(\n 'Media processing timeout',\n 'PROCESSING_TIMEOUT',\n 'x',\n );\n }\n\n /**\n * Set alt text for uploaded media\n * Uses JSON body as required by metadata/create endpoint\n */\n private async setMediaAltText(\n mediaId: string,\n altText: string,\n ): Promise<void> {\n if (this.usesOAuth2()) {\n const response = await this.makeRequest(\n 'POST',\n X_API_MEDIA_METADATA_URL,\n {\n id: mediaId,\n metadata: {\n alt_text: { text: altText },\n },\n },\n );\n\n if (!response.ok) {\n await this.handleError(response);\n }\n return;\n }\n\n const url = `${X_UPLOAD_URL}/media/metadata/create.json`;\n const authHeader = this.generateOAuthSignature('POST', url, {});\n\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: authHeader,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n media_id: mediaId,\n alt_text: { text: altText },\n }),\n });\n\n if (!response.ok) {\n await this.handleError(response);\n }\n }\n\n /**\n * Post link as reply (algorithm-friendly pattern)\n */\n private async postLinkReply(\n parentId: string,\n linkUrl: string,\n ): Promise<void> {\n await this.makeRequest('POST', `${X_API_URL}/tweets`, {\n text: linkUrl,\n reply: { in_reply_to_tweet_id: parentId },\n });\n }\n\n async getPost(postId: string): Promise<Post> {\n const response = await this.makeRequest(\n 'GET',\n `${X_API_URL}/tweets/${postId}?tweet.fields=created_at,public_metrics,attachments&expansions=attachments.media_keys&media.fields=type`,\n );\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n const data = await response.json();\n const tweet = data.data;\n\n return {\n id: tweet.id,\n url: `https://x.com/i/status/${tweet.id}`,\n type: this.resolvePostType(tweet, data.includes?.media),\n description: tweet.text,\n publishedAt: new Date(tweet.created_at),\n visibility: 'public',\n analytics: {\n likes: tweet.public_metrics?.like_count,\n shares: tweet.public_metrics?.retweet_count,\n comments: tweet.public_metrics?.reply_count,\n views: tweet.public_metrics?.impression_count,\n impressions: tweet.public_metrics?.impression_count,\n lastUpdated: new Date(),\n raw: tweet.public_metrics,\n },\n };\n }\n\n async deletePost(postId: string): Promise<void> {\n const response = await this.makeRequest(\n 'DELETE',\n `${X_API_URL}/tweets/${postId}`,\n );\n\n if (!response.ok && response.status !== 200) {\n await this.handleError(response);\n }\n }\n\n async getAnalytics(postId: string): Promise<PostAnalytics> {\n const post = await this.getPost(postId);\n return post.analytics ?? {};\n }\n\n getCapabilities(): PlatformCapabilities {\n return {\n video: true,\n image: true,\n text: true,\n link: true,\n linkAttachment: false,\n scheduling: false, // Would need Twitter Ads API\n analytics: true,\n rawAnalytics: true,\n publishModes: ['dry_run', 'stage_remote', 'public'],\n staging: true,\n privatePublishing: false,\n maxVideoLength: 140, // 2 minutes 20 seconds\n maxVideoSize: 512 * 1024 * 1024, // 512MB\n supportedVideoFormats: ['mp4', 'mov'],\n aspectRatios: ['16:9', '1:1', '9:16'],\n maxTextLength: 280,\n maxHashtags: undefined, // No hard limit\n supportedPostTypes: ['text', 'image', 'video', 'link'],\n };\n }\n\n private resolveLinkBehavior(override?: LinkBehavior): LinkBehavior {\n return override ?? this.config.linkBehavior ?? 'inline';\n }\n\n private usesOAuth2(): boolean {\n if (this.config.authType) {\n return this.config.authType === 'oauth2';\n }\n\n return !this.config.accessSecret;\n }\n\n private requireOAuth1Config(): {\n apiKey: string;\n apiSecret: string;\n accessSecret: string;\n } {\n if (\n !this.config.apiKey ||\n !this.config.apiSecret ||\n !this.config.accessSecret\n ) {\n throw new SocialAuthError('x', 'Missing OAuth 1.0a credentials');\n }\n\n return {\n apiKey: this.config.apiKey,\n apiSecret: this.config.apiSecret,\n accessSecret: this.config.accessSecret,\n };\n }\n\n /**\n * Build post text with hashtags\n */\n private buildPostText(\n text?: string,\n tags?: string[],\n linkUrl?: string,\n linkBehavior: LinkBehavior = 'inline',\n ): string {\n let result = text ?? '';\n const suffixParts: string[] = [];\n\n if (linkUrl && linkBehavior === 'inline') {\n result = result.replace(linkUrl, '').trim();\n suffixParts.push(linkUrl);\n }\n\n if (tags && tags.length > 0) {\n const hashtags = tags.map((t) => (t.startsWith('#') ? t : `#${t}`));\n suffixParts.push(hashtags.join(' '));\n }\n\n const suffix = suffixParts.join('\\n\\n');\n if (!suffix) {\n return this.truncatePostText(result, 280);\n }\n\n const separator = result.length > 0 ? '\\n\\n' : '';\n const suffixBudget = suffix.length + separator.length;\n if (suffixBudget >= 280) {\n return this.truncatePostText(suffix, 280);\n }\n\n const textBudget = 280 - suffixBudget;\n const truncatedText = this.truncatePostText(result, textBudget);\n\n return truncatedText ? `${truncatedText}${separator}${suffix}` : suffix;\n }\n\n private truncatePostText(text: string, maxLength: number): string {\n if (text.length <= maxLength) {\n return text;\n }\n\n if (maxLength <= 3) {\n return text.slice(0, maxLength);\n }\n\n return `${text.slice(0, maxLength - 3)}...`;\n }\n\n private resolvePostType(\n tweet: { attachments?: { media_keys?: string[] } },\n media?: Array<{ media_key?: string; type?: string }>,\n ): Post['type'] {\n const mediaKey = tweet.attachments?.media_keys?.[0];\n if (!mediaKey) {\n return 'text';\n }\n\n const mediaType =\n media?.find((item) => item.media_key === mediaKey)?.type ??\n this.inferMediaTypeFromKey(mediaKey);\n\n if (mediaType === 'video' || mediaType === 'animated_gif') {\n return 'video';\n }\n\n return 'image';\n }\n\n private inferMediaTypeFromKey(mediaKey: string): string | undefined {\n if (mediaKey.startsWith('13_')) return 'video';\n if (mediaKey.startsWith('7_')) return 'animated_gif';\n if (mediaKey.startsWith('3_')) return 'photo';\n return undefined;\n }\n\n /**\n * Make authenticated request to Twitter API v2\n */\n private async makeRequest(\n method: string,\n url: string,\n body?: unknown,\n ): Promise<Response> {\n const parsedUrl = new URL(url);\n const signatureParams = Object.fromEntries(parsedUrl.searchParams);\n const signingUrl = `${parsedUrl.origin}${parsedUrl.pathname}`;\n const authHeader = this.usesOAuth2()\n ? `Bearer ${this.config.accessToken}`\n : this.generateOAuthSignature(method, signingUrl, signatureParams);\n\n const options: RequestInit = {\n method,\n headers: {\n Authorization: authHeader,\n 'Content-Type': 'application/json',\n },\n };\n\n if (body) {\n options.body = JSON.stringify(body);\n }\n\n return fetch(url, options);\n }\n\n private async makeBearerUploadRequest(\n method: string,\n params: FormData | Record<string, string>,\n ): Promise<Response> {\n let url = X_API_MEDIA_UPLOAD_URL;\n const options: RequestInit = {\n method,\n headers: {\n Authorization: `Bearer ${this.config.accessToken}`,\n },\n };\n\n if (method === 'GET' && !(params instanceof FormData)) {\n url = `${url}?${new URLSearchParams(params).toString()}`;\n } else if (params instanceof FormData) {\n options.body = params;\n } else {\n const formData = new FormData();\n for (const [key, value] of Object.entries(params)) {\n formData.append(key, value);\n }\n options.body = formData;\n }\n\n return fetch(url, options);\n }\n\n /**\n * Make authenticated request to Twitter Upload API\n */\n private async makeUploadRequest(\n method: string,\n url: string,\n params: FormData | Record<string, unknown>,\n isFormData = false,\n ): Promise<Response> {\n // For OAuth signature, we need string params (not FormData)\n // Convert Record values to strings for OAuth and URLSearchParams\n const stringParams: Record<string, string> =\n params instanceof FormData\n ? {}\n : Object.fromEntries(\n Object.entries(params).map(([k, v]) => [k, String(v)]),\n );\n const queryParams =\n method === 'GET' || (!isFormData && !(params instanceof FormData))\n ? stringParams\n : {};\n const authHeader = this.generateOAuthSignature(method, url, queryParams);\n\n const options: RequestInit = {\n method,\n headers: {\n Authorization: authHeader,\n },\n };\n\n if (method === 'GET') {\n const queryString = new URLSearchParams(stringParams).toString();\n url = `${url}?${queryString}`;\n } else if (isFormData && params instanceof FormData) {\n options.body = params;\n } else if (!(params instanceof FormData)) {\n options.headers = {\n ...options.headers,\n 'Content-Type': 'application/x-www-form-urlencoded',\n };\n options.body = new URLSearchParams(stringParams).toString();\n }\n\n return fetch(url, options);\n }\n\n /**\n * Handle API errors\n */\n private async handleError(response: Response): Promise<never> {\n const text = await response.text();\n let error;\n try {\n error = JSON.parse(text);\n } catch {\n error = { detail: text };\n }\n\n if (response.status === 401) {\n throw new SocialAuthError('x', error.detail ?? 'Unauthorized');\n }\n\n if (response.status === 429) {\n const resetTime = response.headers.get('x-rate-limit-reset');\n const retryAfter = resetTime\n ? parseInt(resetTime, 10) - Math.floor(Date.now() / 1000)\n : undefined;\n throw new SocialRateLimitError('x', retryAfter);\n }\n\n throw new SocialError(\n error.detail ?? error.title ?? 'API request failed',\n error.type ?? 'API_ERROR',\n 'x',\n response.status,\n );\n }\n}\n","/**\n * YouTube Adapter\n *\n * Implements SocialPlatform interface for YouTube/YouTube Shorts publishing.\n * Uses YouTube Data API v3.\n */\n\nimport { createHash, randomUUID } from 'node:crypto';\n\nimport { createLogger, type Logger } from '@happyvertical/logger';\nimport { resolveMediaData } from '../media.js';\nimport {\n createSafetyResult,\n isPublicPublishMode,\n resolvePublishMode,\n} from '../safety.js';\nimport type {\n AuthorizationOptions,\n AuthorizationResult,\n AuthResult,\n CodeExchangeParams,\n ImagePost,\n LinkPost,\n PlatformCapabilities,\n Post,\n PostAnalytics,\n PostResult,\n SocialPlatform,\n TextPost,\n VideoPost,\n YouTubeConfig,\n} from '../types.js';\nimport {\n SocialAuthError,\n SocialError,\n SocialRateLimitError,\n} from '../types.js';\n\nconst YOUTUBE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';\nconst YOUTUBE_TOKEN_URL = 'https://oauth2.googleapis.com/token';\nconst YOUTUBE_API_URL = 'https://www.googleapis.com/youtube/v3';\nconst YOUTUBE_UPLOAD_URL = 'https://www.googleapis.com/upload/youtube/v3';\n\n/**\n * YouTube video category IDs\n * @see https://developers.google.com/youtube/v3/docs/videoCategories/list\n */\nexport const YOUTUBE_CATEGORIES = {\n FILM_ANIMATION: '1',\n AUTOS_VEHICLES: '2',\n MUSIC: '10',\n PETS_ANIMALS: '15',\n SPORTS: '17',\n TRAVEL_EVENTS: '19',\n GAMING: '20',\n PEOPLE_BLOGS: '22',\n COMEDY: '23',\n ENTERTAINMENT: '24',\n NEWS_POLITICS: '25',\n HOWTO_STYLE: '26',\n EDUCATION: '27',\n SCIENCE_TECH: '28',\n NONPROFITS_ACTIVISM: '29',\n} as const;\n\n/**\n * Default category for video uploads\n */\nconst DEFAULT_CATEGORY_ID = YOUTUBE_CATEGORIES.NEWS_POLITICS;\n\n/**\n * Default scopes for YouTube API\n */\nconst DEFAULT_SCOPES = [\n 'https://www.googleapis.com/auth/youtube.upload',\n 'https://www.googleapis.com/auth/youtube.readonly',\n];\n\ninterface YouTubeApiError {\n error?: {\n code?: number | string;\n message?: string;\n };\n}\n\n/**\n * YouTube adapter for video publishing\n *\n * @example\n * ```typescript\n * const youtube = new YouTubeAdapter({\n * type: 'youtube',\n * clientId: 'your-client-id',\n * clientSecret: 'your-client-secret',\n * accessToken: 'user-access-token',\n * refreshToken: 'user-refresh-token',\n * });\n *\n * const result = await youtube.publishVideo({\n * file: videoBuffer,\n * title: 'Breaking News',\n * description: 'Latest updates...',\n * tags: ['news', 'local'],\n * isShort: true,\n * });\n * ```\n */\nexport class YouTubeAdapter implements SocialPlatform {\n readonly platform = 'youtube' as const;\n private config: YouTubeConfig;\n private logger: Logger;\n private currentAccessToken?: string;\n\n constructor(config: YouTubeConfig) {\n this.config = config;\n this.currentAccessToken = config.accessToken;\n this.logger = createLogger({ level: 'info' });\n }\n\n /**\n * Generate OAuth authorization URL\n */\n getAuthorizationUrl(options: AuthorizationOptions = {}): AuthorizationResult {\n const state = options.state ?? randomUUID();\n const scopes = options.scopes ?? DEFAULT_SCOPES;\n\n // Generate PKCE code verifier and challenge\n const codeVerifier = options.codeVerifier ?? this.generateCodeVerifier();\n const codeChallenge = this.generateCodeChallenge(codeVerifier);\n\n const params = new URLSearchParams({\n client_id: this.config.clientId,\n redirect_uri: options.redirectUri ?? this.config.redirectUri ?? '',\n response_type: 'code',\n scope: scopes.join(' '),\n state,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n access_type: 'offline',\n prompt: 'consent',\n });\n\n return {\n url: `${YOUTUBE_AUTH_URL}?${params}`,\n state,\n codeVerifier,\n };\n }\n\n /**\n * Exchange authorization code for tokens\n */\n async exchangeCode(params: CodeExchangeParams): Promise<AuthResult> {\n const body = new URLSearchParams({\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n code: params.code,\n grant_type: 'authorization_code',\n redirect_uri: params.redirectUri ?? this.config.redirectUri ?? '',\n });\n\n if (params.codeVerifier) {\n body.set('code_verifier', params.codeVerifier);\n }\n\n const response = await fetch(YOUTUBE_TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body,\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new SocialAuthError('youtube', `Token exchange failed: ${error}`);\n }\n\n const data = await response.json();\n\n this.currentAccessToken = data.access_token;\n\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: data.expires_in\n ? new Date(Date.now() + data.expires_in * 1000)\n : undefined,\n tokenType: data.token_type,\n scopes: data.scope?.split(' '),\n };\n }\n\n async authenticate(): Promise<AuthResult> {\n if (!this.config.accessToken) {\n throw new SocialAuthError('youtube', 'No access token configured');\n }\n\n // Verify token is valid\n const response = await fetch(\n `https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=${this.config.accessToken}`,\n );\n\n if (!response.ok) {\n // Try to refresh if we have a refresh token\n if (this.config.refreshToken) {\n return this.refreshToken(this.config.refreshToken);\n }\n throw new SocialAuthError('youtube', 'Invalid access token');\n }\n\n const data = await response.json();\n\n return {\n accessToken: this.config.accessToken,\n expiresAt: data.expires_in\n ? new Date(Date.now() + data.expires_in * 1000)\n : undefined,\n scopes: data.scope?.split(' '),\n };\n }\n\n async refreshToken(refreshToken: string): Promise<AuthResult> {\n const body = new URLSearchParams({\n client_id: this.config.clientId,\n client_secret: this.config.clientSecret,\n refresh_token: refreshToken,\n grant_type: 'refresh_token',\n });\n\n const response = await fetch(YOUTUBE_TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body,\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new SocialAuthError('youtube', `Token refresh failed: ${error}`);\n }\n\n const data = await response.json();\n\n this.currentAccessToken = data.access_token;\n\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token ?? refreshToken,\n expiresAt: data.expires_in\n ? new Date(Date.now() + data.expires_in * 1000)\n : undefined,\n tokenType: data.token_type,\n };\n }\n\n async publishVideo(video: VideoPost): Promise<PostResult> {\n const publishMode = resolvePublishMode(this.config);\n const safeVisibility = isPublicPublishMode(publishMode)\n ? (video.visibility ?? 'public')\n : 'private';\n\n if (publishMode === 'dry_run') {\n return createSafetyResult({\n platform: this.platform,\n mode: publishMode,\n postType: 'video',\n payload: {\n title: video.title ?? 'Untitled',\n description: this.buildDescription(video),\n tags: video.tags,\n categoryId: video.categoryId ?? DEFAULT_CATEGORY_ID,\n privacyStatus: safeVisibility,\n isShort: video.isShort ?? false,\n scheduledAt: video.scheduledAt?.toISOString(),\n },\n note: 'YouTube dry run: no video was uploaded.',\n });\n }\n\n const accessToken = this.currentAccessToken ?? this.config.accessToken;\n if (!accessToken) {\n throw new SocialAuthError('youtube', 'No access token');\n }\n\n // Prepare video metadata\n const metadata = {\n snippet: {\n title: video.title ?? 'Untitled',\n description: this.buildDescription(video),\n tags: video.tags,\n categoryId: video.categoryId ?? DEFAULT_CATEGORY_ID,\n },\n status: {\n privacyStatus: safeVisibility,\n selfDeclaredMadeForKids: false,\n ...(video.scheduledAt && {\n publishAt: video.scheduledAt.toISOString(),\n privacyStatus: 'private', // Must be private for scheduling\n }),\n },\n };\n\n // Get video data.\n const videoData = await resolveMediaData(video.file, {\n explicitMimeType: video.mimeType,\n fallbackMimeType: 'video/mp4',\n });\n\n // Initialize resumable upload\n const initResponse = await fetch(\n `${YOUTUBE_UPLOAD_URL}/videos?uploadType=resumable&part=snippet,status`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': 'application/json',\n 'X-Upload-Content-Type': videoData.mimeType,\n 'X-Upload-Content-Length': videoData.data.length.toString(),\n },\n body: JSON.stringify(metadata),\n },\n );\n\n if (!initResponse.ok) {\n await this.handleError(initResponse);\n }\n\n const uploadUrl = initResponse.headers.get('Location');\n if (!uploadUrl) {\n throw new SocialError(\n 'Failed to get upload URL',\n 'UPLOAD_INIT_FAILED',\n 'youtube',\n );\n }\n\n // Upload video data\n const uploadResponse = await fetch(uploadUrl, {\n method: 'PUT',\n headers: {\n 'Content-Type': videoData.mimeType,\n 'Content-Length': videoData.data.length.toString(),\n },\n body: new Uint8Array(videoData.data),\n });\n\n if (!uploadResponse.ok) {\n await this.handleError(uploadResponse);\n }\n\n const result = await uploadResponse.json();\n\n // Upload thumbnail if provided\n if (video.thumbnail) {\n await this.uploadThumbnail(\n result.id,\n video.thumbnail,\n accessToken,\n video.thumbnailMimeType,\n );\n }\n\n const status: PostResult['status'] = video.scheduledAt\n ? 'scheduled'\n : isPublicPublishMode(publishMode)\n ? 'processing'\n : 'staged';\n\n return {\n id: result.id,\n url: `https://youtube.com/watch?v=${result.id}`,\n status,\n publishedAt: status === 'processing' ? new Date() : undefined,\n scheduledAt: video.scheduledAt,\n metadata: {\n channelId: result.snippet?.channelId,\n channelTitle: result.snippet?.channelTitle,\n publishMode,\n privacyStatus: metadata.status.privacyStatus,\n safety: !isPublicPublishMode(publishMode),\n },\n };\n }\n\n /**\n * Upload custom thumbnail\n * @returns true if upload succeeded, false otherwise\n */\n private async uploadThumbnail(\n videoId: string,\n thumbnail: Buffer | string,\n accessToken: string,\n thumbnailMimeType?: string,\n ): Promise<boolean> {\n try {\n const thumbnailData = await resolveMediaData(thumbnail, {\n explicitMimeType: thumbnailMimeType,\n fallbackMimeType: 'image/png',\n });\n\n const response = await fetch(\n `${YOUTUBE_UPLOAD_URL}/thumbnails/set?videoId=${videoId}`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n 'Content-Type': thumbnailData.mimeType,\n },\n body: new Uint8Array(thumbnailData.data),\n },\n );\n\n if (!response.ok) {\n const errorText = await response.text();\n this.logger.warn('Failed to upload thumbnail', {\n videoId,\n status: response.status,\n error: errorText,\n });\n return false;\n }\n\n return true;\n } catch (error) {\n this.logger.warn('Thumbnail upload error', {\n videoId,\n error: error instanceof Error ? error.message : String(error),\n });\n return false;\n }\n }\n\n async publishImage(_image: ImagePost): Promise<PostResult> {\n throw new SocialError(\n 'YouTube does not support image-only posts',\n 'NOT_SUPPORTED',\n 'youtube',\n );\n }\n\n async publishText(_text: TextPost): Promise<PostResult> {\n throw new SocialError(\n 'YouTube does not support text-only posts',\n 'NOT_SUPPORTED',\n 'youtube',\n );\n }\n\n async publishLink(_link: LinkPost): Promise<PostResult> {\n throw new SocialError(\n 'YouTube does not support link-only posts',\n 'NOT_SUPPORTED',\n 'youtube',\n );\n }\n\n async getPost(postId: string): Promise<Post> {\n const accessToken = this.currentAccessToken ?? this.config.accessToken;\n if (!accessToken) {\n throw new SocialAuthError('youtube', 'No access token');\n }\n\n const response = await fetch(\n `${YOUTUBE_API_URL}/videos?id=${postId}&part=snippet,status,statistics`,\n {\n headers: { Authorization: `Bearer ${accessToken}` },\n },\n );\n\n if (!response.ok) {\n await this.handleError(response);\n }\n\n const data = await response.json();\n const video = data.items?.[0];\n\n if (!video) {\n throw new SocialError('Video not found', 'NOT_FOUND', 'youtube', 404);\n }\n\n const statistics = video.statistics ?? {};\n\n return {\n id: video.id,\n url: `https://youtube.com/watch?v=${video.id}`,\n type: 'video',\n title: video.snippet.title,\n description: video.snippet.description,\n publishedAt: new Date(video.snippet.publishedAt),\n visibility: video.status.privacyStatus,\n analytics: {\n views: this.parseMetric(statistics.viewCount),\n likes: this.parseMetric(statistics.likeCount),\n comments: this.parseMetric(statistics.commentCount),\n lastUpdated: new Date(),\n raw: statistics,\n },\n };\n }\n\n async deletePost(postId: string): Promise<void> {\n const accessToken = this.currentAccessToken ?? this.config.accessToken;\n if (!accessToken) {\n throw new SocialAuthError('youtube', 'No access token');\n }\n\n const response = await fetch(`${YOUTUBE_API_URL}/videos?id=${postId}`, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${accessToken}` },\n });\n\n if (!response.ok && response.status !== 204) {\n await this.handleError(response);\n }\n }\n\n async getAnalytics(postId: string): Promise<PostAnalytics> {\n const post = await this.getPost(postId);\n return post.analytics ?? {};\n }\n\n getCapabilities(): PlatformCapabilities {\n return {\n video: true,\n image: false,\n text: false,\n link: false,\n scheduling: true,\n analytics: true,\n rawAnalytics: true,\n publishModes: ['dry_run', 'private_or_scheduled', 'public'],\n staging: false,\n privatePublishing: true,\n maxVideoLength: 60 * 60 * 12, // 12 hours (with verification)\n maxVideoSize: 256 * 1024 * 1024 * 1024, // 256GB\n supportedVideoFormats: ['mp4', 'mov', 'avi', 'wmv', 'flv', 'webm'],\n aspectRatios: ['16:9', '9:16', '1:1', '4:3'],\n maxTextLength: 5000, // Description\n maxHashtags: 15,\n supportedPostTypes: ['video'],\n };\n }\n\n /**\n * Build description with link and hashtags\n */\n private buildDescription(video: VideoPost): string {\n let description = video.description ?? '';\n\n if (video.linkUrl) {\n description += `\\n\\n${video.linkUrl}`;\n }\n\n const tags = video.isShort\n ? Array.from(new Set([...(video.tags ?? []), 'Shorts']))\n : video.tags;\n\n if (tags && tags.length > 0) {\n const hashtags = tags.map((t) => (t.startsWith('#') ? t : `#${t}`));\n description += `\\n\\n${hashtags.join(' ')}`;\n }\n\n return description;\n }\n\n private parseMetric(value: unknown): number | undefined {\n if (value === undefined || value === null || value === '') return undefined;\n const parsed = Number.parseInt(String(value), 10);\n return Number.isFinite(parsed) ? parsed : undefined;\n }\n\n /**\n * Handle API errors\n */\n private async handleError(response: Response): Promise<never> {\n const text = await response.text();\n let error: YouTubeApiError;\n try {\n error = JSON.parse(text) as YouTubeApiError;\n } catch {\n error = { error: { message: text } };\n }\n\n if (response.status === 401) {\n throw new SocialAuthError(\n 'youtube',\n error.error?.message ?? 'Unauthorized',\n );\n }\n\n if (response.status === 429) {\n const retryAfter = response.headers.get('Retry-After');\n throw new SocialRateLimitError(\n 'youtube',\n retryAfter ? parseInt(retryAfter, 10) : undefined,\n );\n }\n\n throw new SocialError(\n error.error?.message ?? 'API request failed',\n error.error?.code?.toString() ?? 'API_ERROR',\n 'youtube',\n response.status,\n );\n }\n\n /**\n * Generate PKCE code verifier\n */\n private generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return btoa(String.fromCharCode(...array))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '');\n }\n\n /**\n * Generate PKCE code challenge from verifier using S256\n */\n private generateCodeChallenge(verifier: string): string {\n const hash = createHash('sha256').update(verifier).digest();\n return Buffer.from(hash)\n .toString('base64')\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '');\n }\n}\n","/**\n * @happyvertical/social\n *\n * Unified social platform publishing SDK.\n * Supports YouTube, Threads, X (Twitter), and Bluesky.\n */\n\nexport { BlueskyAdapter } from './adapters/bluesky.js';\nexport { FacebookPageAdapter } from './adapters/facebook.js';\nexport { ThreadsAdapter } from './adapters/threads.js';\nexport { XAdapter } from './adapters/x.js';\nexport { YouTubeAdapter } from './adapters/youtube.js';\nexport * from './types.js';\n\nimport { BlueskyAdapter } from './adapters/bluesky.js';\nimport { FacebookPageAdapter } from './adapters/facebook.js';\nimport { ThreadsAdapter } from './adapters/threads.js';\nimport { XAdapter } from './adapters/x.js';\nimport { YouTubeAdapter } from './adapters/youtube.js';\nimport type { SocialConfig, SocialPlatform } from './types.js';\nimport { SocialError } from './types.js';\n\n/**\n * Factory function to create a social platform adapter\n *\n * @example\n * ```typescript\n * import { getSocial } from '@happyvertical/social';\n *\n * // YouTube\n * const youtube = await getSocial({\n * type: 'youtube',\n * clientId: 'your-client-id',\n * clientSecret: 'your-client-secret',\n * accessToken: 'user-access-token',\n * });\n *\n * // Bluesky\n * const bluesky = await getSocial({\n * type: 'bluesky',\n * identifier: 'handle.bsky.social',\n * password: 'app-password',\n * });\n *\n * // X (Twitter)\n * const x = await getSocial({\n * type: 'x',\n * apiKey: 'consumer-key',\n * apiSecret: 'consumer-secret',\n * accessToken: 'user-access-token',\n * accessSecret: 'user-access-secret',\n * });\n *\n * // Threads\n * const threads = await getSocial({\n * type: 'threads',\n * accessToken: 'user-access-token',\n * userId: 'threads-user-id',\n * });\n *\n * // Publish text\n * const result = await youtube.publishText({\n * text: 'Hello from Bentley!',\n * });\n * ```\n */\nexport async function getSocial(config: SocialConfig): Promise<SocialPlatform> {\n let adapter: SocialPlatform;\n\n switch (config.type) {\n case 'youtube':\n adapter = new YouTubeAdapter(config);\n break;\n case 'threads':\n adapter = new ThreadsAdapter(config);\n break;\n case 'facebook':\n adapter = new FacebookPageAdapter(config);\n break;\n case 'x':\n adapter = new XAdapter(config);\n break;\n case 'bluesky':\n adapter = new BlueskyAdapter(config);\n break;\n default:\n throw new SocialError(\n `Unknown platform type: ${(config as { type?: string }).type}`,\n 'UNKNOWN_PLATFORM',\n );\n }\n\n return adapter;\n}\n\n/**\n * Create multiple social platform adapters at once\n *\n * @example\n * ```typescript\n * import { getSocialMulti } from '@happyvertical/social';\n *\n * const adapters = await getSocialMulti([\n * { type: 'youtube', clientId: '...', clientSecret: '...', accessToken: '...' },\n * { type: 'bluesky', identifier: '...', password: '...' },\n * ]);\n *\n * // Publish to all platforms\n * const results = await Promise.all(\n * adapters.map(adapter => adapter.publishText({ text: 'Hello!' }))\n * );\n * ```\n */\nexport async function getSocialMulti(\n configs: SocialConfig[],\n): Promise<SocialPlatform[]> {\n return Promise.all(configs.map(getSocial));\n}\n\n/**\n * Publish content to multiple platforms at once\n *\n * @example\n * ```typescript\n * import { publishToAll, getSocialMulti } from '@happyvertical/social';\n *\n * const adapters = await getSocialMulti([...]);\n *\n * const results = await publishToAll(adapters, {\n * type: 'text',\n * text: 'Breaking news from Bentley!',\n * linkUrl: 'https://example.com/article',\n * tags: ['news', 'local'],\n * });\n * ```\n */\nexport async function publishToAll(\n adapters: SocialPlatform[],\n content: {\n type: 'text' | 'image' | 'video' | 'link';\n text?: string;\n description?: string;\n file?: Buffer | string;\n title?: string;\n linkUrl?: string;\n url?: string;\n tags?: string[];\n altText?: string;\n mimeType?: string;\n thumbnail?: Buffer | string;\n thumbnailMimeType?: string;\n },\n): Promise<Map<string, { success: boolean; result?: unknown; error?: Error }>> {\n const results = new Map<\n string,\n { success: boolean; result?: unknown; error?: Error }\n >();\n\n await Promise.all(\n adapters.map(async (adapter) => {\n try {\n let result: unknown;\n\n switch (content.type) {\n case 'text':\n result = await adapter.publishText({\n text: content.text ?? content.description ?? '',\n linkUrl: content.linkUrl,\n tags: content.tags,\n });\n break;\n case 'link': {\n const url = content.url ?? content.linkUrl;\n if (!url) {\n throw new SocialError(\n 'Link URL required',\n 'MISSING_LINK_URL',\n adapter.platform,\n );\n }\n result = await adapter.publishLink({\n url,\n text: content.text,\n title: content.title,\n description: content.description,\n tags: content.tags,\n });\n break;\n }\n case 'image':\n if (!content.file) {\n throw new SocialError(\n 'Image file required',\n 'MISSING_FILE',\n adapter.platform,\n );\n }\n result = await adapter.publishImage({\n file: content.file,\n description: content.description ?? content.text,\n linkUrl: content.linkUrl,\n tags: content.tags,\n altText: content.altText,\n mimeType: content.mimeType,\n });\n break;\n case 'video':\n if (!content.file) {\n throw new SocialError(\n 'Video file required',\n 'MISSING_FILE',\n adapter.platform,\n );\n }\n result = await adapter.publishVideo({\n file: content.file,\n title: content.title,\n description: content.description ?? content.text,\n linkUrl: content.linkUrl,\n tags: content.tags,\n mimeType: content.mimeType,\n thumbnail: content.thumbnail,\n thumbnailMimeType: content.thumbnailMimeType,\n });\n break;\n }\n\n results.set(adapter.platform, { success: true, result });\n } catch (error) {\n results.set(adapter.platform, {\n success: false,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n }),\n );\n\n return results;\n}\n"],"names":["response","YouTubeAdapter","ThreadsAdapter","FacebookPageAdapter","XAdapter","BlueskyAdapter"],"mappings":"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"}
|
package/metadata.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@happyvertical/social",
|
|
3
|
+
"path": "packages/social",
|
|
4
|
+
"position": {
|
|
5
|
+
"index": 25,
|
|
6
|
+
"count": 30
|
|
7
|
+
},
|
|
8
|
+
"description": "Social platform adapters for publishing to YouTube, Threads, X, and Bluesky",
|
|
9
|
+
"provides": [
|
|
10
|
+
"Social platform adapters for publishing to YouTube, Threads, X, and Bluesky"
|
|
11
|
+
],
|
|
12
|
+
"implements": [],
|
|
13
|
+
"requires": {
|
|
14
|
+
"workspace": [
|
|
15
|
+
"@happyvertical/logger",
|
|
16
|
+
"@happyvertical/utils"
|
|
17
|
+
],
|
|
18
|
+
"externalHappyVertical": [],
|
|
19
|
+
"external": []
|
|
20
|
+
},
|
|
21
|
+
"dependents": [],
|
|
22
|
+
"stability": {
|
|
23
|
+
"level": "stable",
|
|
24
|
+
"reason": "Primary package surface is described as implemented and production-oriented."
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"social"
|
|
28
|
+
]
|
|
29
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@happyvertical/social",
|
|
3
|
+
"version": "0.74.8",
|
|
4
|
+
"description": "Social platform adapters for publishing to YouTube, Facebook Pages, Threads, X, and Bluesky",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@happyvertical/logger": "0.74.8",
|
|
16
|
+
"@happyvertical/utils": "0.74.8"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "25.0.10",
|
|
20
|
+
"typescript": "^5.9.3",
|
|
21
|
+
"vite": "7.3.2",
|
|
22
|
+
"vite-plugin-dts": "4.5.4",
|
|
23
|
+
"vitest": "^4.1.5"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"AGENT.md",
|
|
30
|
+
"metadata.json"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"registry": "https://registry.npmjs.org",
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/happyvertical/sdk.git",
|
|
39
|
+
"directory": "packages/social"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/happyvertical/sdk/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/happyvertical/sdk/tree/main/packages/social#readme",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"scripts": {
|
|
47
|
+
"test": "npx vitest run",
|
|
48
|
+
"test:live": "HV_SOCIAL_LIVE_SMOKE=1 npx vitest run src/live-smoke.optional.test.ts",
|
|
49
|
+
"test:watch": "npx vitest",
|
|
50
|
+
"build": "vite build",
|
|
51
|
+
"build:watch": "vite build --watch",
|
|
52
|
+
"clean": "rm -rf dist",
|
|
53
|
+
"dev": "npm run build:watch & npm run test:watch"
|
|
54
|
+
}
|
|
55
|
+
}
|