mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-22 14:38:11 +02:00
feat(tiktok): publier les reels sur les comptes TikTok des magasins
Ajoute la gestion multi-comptes TikTok et permet d'envoyer la même vidéo sur plusieurs pages Facebook ET plusieurs comptes TikTok en une seule publication. Un compte TikTok vit dans social_pages comme une page Facebook : il hérite donc des permissions par utilisateur, de la planification et du calendrier. Trois particularités de l'API TikTok structurent l'implémentation : - pas de jeton saisi à la main : chaque compte passe par OAuth, et l'access token (24h) est renouvelé via le refresh token (1 an) avant chaque publication ; - la publication est asynchrone : l'upload rend un publish_id, et un poller récupère l'identifiant définitif du post ; - creator_info doit être interrogé avant chaque envoi pour ne demander qu'un niveau de confidentialité réellement autorisé sur le compte. Serveur : - service TikTok (OAuth, creator_info, upload FILE_UPLOAD par chunks, suivi de statut) et routes de connexion/configuration - schéma : platform 'tiktok', refresh token et scopes sur social_pages, publish_id/publish_status sur scheduled_posts, table tiktok_config - routage par plateforme dans les deux flux de reels et le planificateur - cron de suivi des publications, token manager et analytics adaptés Client : - connexion et reconnexion des comptes TikTok depuis « Pages gérées » - sélection combinée pages Facebook / comptes TikTok à la publication - configuration de l'application TikTok dans les paramètres (admin) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018uUPZ1GNpEkYB1dpXHYPek
This commit is contained in:
27 files changed
+1679
-183
No files matched your search
@@ -1,7 +1,8 @@
|
||||
import cron, { ScheduledTask } from "node-cron";
|
||||
import { storage } from "../storage";
|
||||
import { facebookService } from "./facebook";
|
||||
import { minioService as storageService } from "./minio";
|
||||
import { tiktokService } from "./tiktok";
|
||||
import { minioService as storageService, readMediaBuffer } from "./minio";
|
||||
|
||||
export class SchedulerService {
|
||||
private task: ScheduledTask | null = null;
|
||||
@@ -94,11 +95,28 @@ export class SchedulerService {
|
||||
return;
|
||||
}
|
||||
|
||||
// Publish to Facebook/Instagram
|
||||
let externalPostId: string;
|
||||
|
||||
// Publish to Facebook/Instagram/TikTok
|
||||
if (page.platform === 'facebook') {
|
||||
externalPostId = await facebookService.publishPost(post, page, scheduledPost.postType, mediaList);
|
||||
const externalPostId = await facebookService.publishPost(post, page, scheduledPost.postType, mediaList);
|
||||
|
||||
// Update the scheduled post as published (clear any previous error)
|
||||
await storage.updateScheduledPost(scheduledPost.id, {
|
||||
publishedAt: new Date(),
|
||||
externalPostId,
|
||||
error: null,
|
||||
});
|
||||
} else if (page.platform === 'tiktok') {
|
||||
const publishId = await this.publishToTiktok(post, page, mediaList);
|
||||
|
||||
// TikTok publie de façon asynchrone : on marque l'envoi comme effectué pour
|
||||
// ne pas republier à la minute suivante, et le poller de statut renseignera
|
||||
// l'identifiant définitif du post.
|
||||
await storage.updateScheduledPost(scheduledPost.id, {
|
||||
publishedAt: new Date(),
|
||||
publishId,
|
||||
publishStatus: 'PROCESSING_UPLOAD',
|
||||
error: null,
|
||||
});
|
||||
} else if (page.platform === 'instagram') {
|
||||
// Instagram publishing not yet implemented
|
||||
console.warn(`Instagram publishing not yet implemented for post ${post.id}`);
|
||||
@@ -113,13 +131,6 @@ export class SchedulerService {
|
||||
throw new Error(`Unsupported platform: ${page.platform}`);
|
||||
}
|
||||
|
||||
// Update the scheduled post as published (clear any previous error)
|
||||
await storage.updateScheduledPost(scheduledPost.id, {
|
||||
publishedAt: new Date(),
|
||||
externalPostId,
|
||||
error: null,
|
||||
});
|
||||
|
||||
// Update post status
|
||||
await storage.updatePost(post.id, {
|
||||
status: "published",
|
||||
@@ -143,7 +154,28 @@ export class SchedulerService {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Successfully published post ${post.id} with external ID: ${externalPostId}`);
|
||||
console.log(`Successfully published post ${post.id} to ${page.platform} page ${page.pageName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie la vidéo d'un post planifié sur un compte TikTok.
|
||||
* TikTok ne sait pas programmer une publication : c'est notre planificateur qui
|
||||
* déclenche l'envoi à l'heure voulue.
|
||||
*
|
||||
* @returns le publish_id à suivre pour connaître le résultat final
|
||||
*/
|
||||
private async publishToTiktok(post: any, page: any, mediaList: any[]): Promise<string> {
|
||||
const videoMedia = mediaList.find(m => m.type === 'video');
|
||||
|
||||
if (!videoMedia) {
|
||||
throw new Error(
|
||||
`Le compte TikTok "${page.pageName}" ne peut recevoir que des vidéos : ce post n'en contient pas.`
|
||||
);
|
||||
}
|
||||
|
||||
const videoBuffer = await readMediaBuffer(videoMedia.originalUrl);
|
||||
|
||||
return await tiktokService.publishVideoFromBuffer(page, videoBuffer, post.content);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user