Add ability to post videos to Facebook feeds and stories

Adds support for publishing video content to Facebook feeds and stories via new API endpoints and service methods, addressing issues with video previews and direct video uploads.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: ae4037a0-2a6f-4530-9bac-79b543286bda
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/397bca8c-984f-43ff-841a-10897aeb8140/ae4037a0-2a6f-4530-9bac-79b543286bda/s8IyLQK
This commit is contained in:
michaelschal committed 2025-10-13 12:05:51 +00:00
1 parent 68ed5d52d4
commit b1dc815954
1 file changed
+63 -6
+63 -6
View File
@@ -10,6 +10,11 @@ interface FacebookFeedResponse {
id: string; id: string;
} }
interface FacebookVideoResponse {
id: string;
post_id?: string;
}
interface FacebookError { interface FacebookError {
error: { error: {
message: string; message: string;
@@ -54,8 +59,8 @@ export class FacebookService {
return storyIds.join(','); return storyIds.join(',');
} else { } else {
// Default to feed // Default to feed
// Count images only (videos not supported in multi-photo carousel)
const imageMedia = mediaList.filter(m => m.type === 'image'); const imageMedia = mediaList.filter(m => m.type === 'image');
const videoMedia = mediaList.filter(m => m.type === 'video');
if (imageMedia.length > 1) { if (imageMedia.length > 1) {
// Multi-photo carousel (images only) // Multi-photo carousel (images only)
@@ -63,11 +68,9 @@ export class FacebookService {
} else if (imageMedia.length === 1) { } else if (imageMedia.length === 1) {
// Single image (even if mixed with videos, prioritize the image) // Single image (even if mixed with videos, prioritize the image)
return await this.publishPhotoPost(post, page, imageMedia[0]); return await this.publishPhotoPost(post, page, imageMedia[0]);
} else if (mediaList.length >= 1) { } else if (videoMedia.length >= 1) {
// No images, but has media (likely video) - publish first media // Video post - use video endpoint
// Note: Facebook /photos endpoint doesn't support videos well, this may fail return await this.publishVideoPost(post, page, videoMedia[0]);
console.warn(`Publishing non-image media to Facebook feed - this may fail`);
return await this.publishPhotoPost(post, page, mediaList[0]);
} else { } else {
// Text only // Text only
return await this.publishTextPost(post, page); return await this.publishTextPost(post, page);
@@ -122,6 +125,32 @@ export class FacebookService {
return data.post_id || data.id; return data.post_id || data.id;
} }
private async publishVideoPost(post: Post, page: SocialPage, media: Media): Promise<string> {
// Use original URL for video
const videoUrl = media.originalUrl;
const params = new URLSearchParams({
access_token: page.accessToken!,
description: post.content,
file_url: videoUrl,
});
const url = `${this.baseUrl}/${page.pageId}/videos?${params.toString()}`;
const response = await fetch(url, {
method: 'POST',
});
if (!response.ok) {
const error = await response.json() as FacebookError;
throw new Error(`Facebook API error publishing video: ${error.error.message} (code: ${error.error.code})`);
}
const data = await response.json() as FacebookVideoResponse;
// Return the post_id or id
return data.post_id || data.id;
}
private async publishMultiPhotoPost(post: Post, page: SocialPage, mediaList: Media[]): Promise<string> { private async publishMultiPhotoPost(post: Post, page: SocialPage, mediaList: Media[]): Promise<string> {
// Validate that all media are images // Validate that all media are images
const imageMedia = mediaList.filter(m => m.type === 'image'); const imageMedia = mediaList.filter(m => m.type === 'image');
@@ -279,6 +308,10 @@ export class FacebookService {
} }
private async publishStory(post: Post, page: SocialPage, media: Media): Promise<string> { private async publishStory(post: Post, page: SocialPage, media: Media): Promise<string> {
if (media.type === 'video') {
return this.publishVideoStory(post, page, media);
}
// Stories require a 2-step process: // Stories require a 2-step process:
// Step 1: Upload photo as unpublished to get photo_id // Step 1: Upload photo as unpublished to get photo_id
// Step 2: Publish the photo as a story using the photo_id // Step 2: Publish the photo as a story using the photo_id
@@ -326,6 +359,30 @@ export class FacebookService {
const storyData = await storyResponse.json() as { success: boolean; post_id: string }; const storyData = await storyResponse.json() as { success: boolean; post_id: string };
return storyData.post_id; return storyData.post_id;
} }
private async publishVideoStory(post: Post, page: SocialPage, media: Media): Promise<string> {
// Video stories can be published directly to the video_stories endpoint
const videoUrl = media.originalUrl;
const params = new URLSearchParams({
access_token: page.accessToken!,
file_url: videoUrl,
});
const url = `${this.baseUrl}/${page.pageId}/video_stories?${params.toString()}`;
const response = await fetch(url, {
method: 'POST',
});
if (!response.ok) {
const error = await response.json() as FacebookError;
throw new Error(`Facebook API error publishing video story: ${error.error.message} (code: ${error.error.code})`);
}
const data = await response.json() as { success: boolean; post_id: string };
return data.post_id;
}
} }
export const facebookService = new FacebookService(); export const facebookService = new FacebookService();