mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-11 17:26:45 +02:00
5 files changed
+165
-24
No files matched your search
+72
@@ -0,0 +1,72 @@
|
||||
at async Timeout._onTimeout (/app/node_modules/node-cron/dist/esm/scheduler/runner.js:70:44)
|
||||
|
||||
Publishing post 6439b99f-d5eb-4774-b7ea-97c9be8bb3bf to facebook page Frouard
|
||||
|
||||
Publishing non-image media to Facebook feed - this may fail
|
||||
|
||||
Error publishing post aea0ba94-8e1a-4923-9942-6940029f6be5: Error: Facebook API error: Invalid parameter (code: 100)
|
||||
|
||||
at FacebookService.publishPhotoPost (file:///app/dist/index.js:2029:13)
|
||||
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
|
||||
|
||||
at async FacebookService.publishPost (file:///app/dist/index.js:1994:16)
|
||||
|
||||
at async SchedulerService.publishPost (file:///app/dist/index.js:2249:24)
|
||||
|
||||
at async SchedulerService.processPendingPosts (file:///app/dist/index.js:2211:11)
|
||||
|
||||
at async file:///app/dist/index.js:2187:7
|
||||
|
||||
at async Timeout._onTimeout (/app/node_modules/node-cron/dist/esm/scheduler/runner.js:70:44)
|
||||
|
||||
12:52:05 PM [express] GET /api/stats 304 in 29ms :: {"scheduledPosts":9,"scheduledPostsChange":"+100%…
|
||||
|
||||
12:52:05 PM [express] GET /api/scheduled-posts 200 in 33ms :: [{"id":"9128ce3e-88b5-4afe-9834-e79e2da…
|
||||
|
||||
12:52:06 PM [express] GET /api/media 304 in 11ms :: [{"id":"d501254d-4892-4d63-8900-e790ab3794e8","us…
|
||||
|
||||
Publishing post 6439b99f-d5eb-4774-b7ea-97c9be8bb3bf to facebook page Houdemont
|
||||
|
||||
Publishing non-image media to Facebook feed - this may fail
|
||||
|
||||
Error publishing post 23173c8a-7843-48e7-876f-3353988b82a0: Error: Facebook API error: (#200) The permission(s) publish_actions are not available. It has been deprecated. If you want to provide a way for your app users to share content to Facebook, we encourage you to use our Sharing products instead. (code: 200)
|
||||
|
||||
at FacebookService.publishPhotoPost (file:///app/dist/index.js:2029:13)
|
||||
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
|
||||
|
||||
at async FacebookService.publishPost (file:///app/dist/index.js:1994:16)
|
||||
|
||||
at async SchedulerService.publishPost (file:///app/dist/index.js:2249:24)
|
||||
|
||||
at async SchedulerService.processPendingPosts (file:///app/dist/index.js:2211:11)
|
||||
|
||||
at async file:///app/dist/index.js:2187:7
|
||||
|
||||
at async Timeout._onTimeout (/app/node_modules/node-cron/dist/esm/scheduler/runner.js:70:44)
|
||||
|
||||
Publishing post 6439b99f-d5eb-4774-b7ea-97c9be8bb3bf to facebook page Frouard
|
||||
|
||||
Publishing non-image media to Facebook feed - this may fail
|
||||
|
||||
Error publishing post aea0ba94-8e1a-4923-9942-6940029f6be5: Error: Facebook API error: Invalid parameter (code: 100)
|
||||
|
||||
at FacebookService.publishPhotoPost (file:///app/dist/index.js:2029:13)
|
||||
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
|
||||
|
||||
at async FacebookService.publishPost (file:///app/dist/index.js:1994:16)
|
||||
|
||||
at async SchedulerService.publishPost (file:///app/dist/index.js:2249:24)
|
||||
|
||||
at async SchedulerService.processPendingPosts (file:///app/dist/index.js:2211:11)
|
||||
|
||||
at async file:///app/dist/index.js:2187:7
|
||||
|
||||
at async Timeout._onTimeout (/app/node_modules/node-cron/dist/esm/scheduler/runner.js:70:44)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
@@ -250,15 +250,15 @@ export default function MediaUpload() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) as any)}
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Élément sentinelle pour le scroll infini */}
|
||||
{mediaList && visibleCount < (mediaList as any[]).length && (
|
||||
{(mediaList && visibleCount < (mediaList as any[]).length) ? (
|
||||
<div ref={loadMoreRef} className="flex justify-center py-4 mt-3">
|
||||
<Loader2 className="w-5 h-5 text-primary animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -64,6 +64,30 @@ export function PreviewModal({
|
||||
}
|
||||
};
|
||||
|
||||
const renderMedia = (media: Media, className: string = "w-full h-auto") => {
|
||||
const url = getMediaUrl(media);
|
||||
|
||||
if (media.type === 'video') {
|
||||
return (
|
||||
<video
|
||||
src={url}
|
||||
className={className}
|
||||
controls
|
||||
playsInline
|
||||
data-testid="video-preview"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
src={url}
|
||||
alt={`Photo ${currentPhotoIndex + 1}`}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderFacebookFeed = () => (
|
||||
<div className="bg-white rounded-lg overflow-hidden border border-gray-200 max-w-lg mx-auto">
|
||||
{/* Header */}
|
||||
@@ -87,11 +111,7 @@ export function PreviewModal({
|
||||
{/* Media Carousel */}
|
||||
{orderedMedia.length > 0 && (
|
||||
<div className="relative bg-black">
|
||||
<img
|
||||
src={getMediaUrl(orderedMedia[currentPhotoIndex])}
|
||||
alt={`Photo ${currentPhotoIndex + 1}`}
|
||||
className="w-full h-auto"
|
||||
/>
|
||||
{renderMedia(orderedMedia[currentPhotoIndex], "w-full h-auto")}
|
||||
{orderedMedia.length > 1 && (
|
||||
<>
|
||||
<button
|
||||
@@ -149,11 +169,7 @@ export function PreviewModal({
|
||||
{/* Media Carousel */}
|
||||
{orderedMedia.length > 0 && (
|
||||
<div className="relative bg-black aspect-square">
|
||||
<img
|
||||
src={getMediaUrl(orderedMedia[currentPhotoIndex])}
|
||||
alt={`Photo ${currentPhotoIndex + 1}`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
{renderMedia(orderedMedia[currentPhotoIndex], "w-full h-full object-cover")}
|
||||
{orderedMedia.length > 1 && (
|
||||
<>
|
||||
<button
|
||||
@@ -221,11 +237,7 @@ export function PreviewModal({
|
||||
{/* Media */}
|
||||
{orderedMedia.length > 0 && (
|
||||
<div className="relative h-full">
|
||||
<img
|
||||
src={getMediaUrl(orderedMedia[currentPhotoIndex])}
|
||||
alt={`Story ${currentPhotoIndex + 1}`}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
{renderMedia(orderedMedia[currentPhotoIndex], "w-full h-full object-cover")}
|
||||
{orderedMedia.length > 1 && (
|
||||
<>
|
||||
<div className="absolute top-3 left-3 right-3 flex gap-1">
|
||||
|
||||
@@ -10,6 +10,11 @@ interface FacebookFeedResponse {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface FacebookVideoResponse {
|
||||
id: string;
|
||||
post_id?: string;
|
||||
}
|
||||
|
||||
interface FacebookError {
|
||||
error: {
|
||||
message: string;
|
||||
@@ -54,8 +59,8 @@ export class FacebookService {
|
||||
return storyIds.join(',');
|
||||
} else {
|
||||
// Default to feed
|
||||
// Count images only (videos not supported in multi-photo carousel)
|
||||
const imageMedia = mediaList.filter(m => m.type === 'image');
|
||||
const videoMedia = mediaList.filter(m => m.type === 'video');
|
||||
|
||||
if (imageMedia.length > 1) {
|
||||
// Multi-photo carousel (images only)
|
||||
@@ -63,11 +68,9 @@ export class FacebookService {
|
||||
} else if (imageMedia.length === 1) {
|
||||
// Single image (even if mixed with videos, prioritize the image)
|
||||
return await this.publishPhotoPost(post, page, imageMedia[0]);
|
||||
} else if (mediaList.length >= 1) {
|
||||
// No images, but has media (likely video) - publish first media
|
||||
// Note: Facebook /photos endpoint doesn't support videos well, this may fail
|
||||
console.warn(`Publishing non-image media to Facebook feed - this may fail`);
|
||||
return await this.publishPhotoPost(post, page, mediaList[0]);
|
||||
} else if (videoMedia.length >= 1) {
|
||||
// Video post - use video endpoint
|
||||
return await this.publishVideoPost(post, page, videoMedia[0]);
|
||||
} else {
|
||||
// Text only
|
||||
return await this.publishTextPost(post, page);
|
||||
@@ -122,6 +125,32 @@ export class FacebookService {
|
||||
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> {
|
||||
// Validate that all media are images
|
||||
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> {
|
||||
if (media.type === 'video') {
|
||||
return this.publishVideoStory(post, page, media);
|
||||
}
|
||||
|
||||
// Stories require a 2-step process:
|
||||
// Step 1: Upload photo as unpublished to get 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 };
|
||||
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();
|
||||
Reference in new issue
Block a user