diff --git a/backend/src/modules/marketing/marketing.controller.ts b/backend/src/modules/marketing/marketing.controller.ts index 9603c7d..eb8fb17 100644 --- a/backend/src/modules/marketing/marketing.controller.ts +++ b/backend/src/modules/marketing/marketing.controller.ts @@ -88,6 +88,14 @@ export class MarketingController { return this.marketingService.generateContent(user.tenantId, body.prompt); } + @Post('publish-facebook') + @HttpCode(HttpStatus.OK) + async publishFacebook( + @Body() body: { topic: string; imageUrl?: string }, + ) { + return this.marketingService.generateAndPublishFacebook(body.topic, body.imageUrl); + } + @Delete('campaigns/:id') @HttpCode(HttpStatus.NO_CONTENT) async deleteCampaign(@Param('id') id: string) { diff --git a/backend/src/modules/marketing/marketing.service.ts b/backend/src/modules/marketing/marketing.service.ts index e76a8ab..b82d27e 100644 --- a/backend/src/modules/marketing/marketing.service.ts +++ b/backend/src/modules/marketing/marketing.service.ts @@ -82,6 +82,57 @@ export class MarketingService { } } + async generateAndPublishFacebook(promptTopic: string, imageUrl?: string): Promise<{ ok: boolean; postId?: string; postContent: string }> { + const pageId = process.env.META_PAGE_ID || '1047354938453381'; + const accessToken = (process.env.META_ACCESS_TOKEN || '').replace(/\s+/g, ''); + + if (!accessToken) { + throw new Error('META_ACCESS_TOKEN is not configured in backend environment.'); + } + + const systemPrompt = `أنت مدير تسويق ومحرر إعلاني محترف لمنصة Tripz (منصة SaaS عالمية لإطلاق تطبيقات نقل الركاب وإدارة أساطيل التكسي بعلامة المشغّل التجارية). +قواعد التسويق الحاكمة: +1. الملكية: علامتك أنت، وتطبيقك باسمك وشعارك، وبياناتك تبقى لك بعقد تصدير مكتوب. +2. الشفافية: أسعارنا معلنة بالكامل دون أسرار ولا مفاجآت في الفاتورة. +3. السرعة: إطلاق خلال 30 يوماً من التوقيع على آبل وجوجل. +4. الإثبات: محرك مجرّب منذ 2019 يخدم أكثر من 6,000 سائق نشط. +5. دعوة للإجراء (CTA): رابط حاسبة التوفير (https://tripz-egypt.com/apps/) ورابط واتساب المباشر (https://wa.me/962798583052). + +اكتب منشوراً جذاباً جداً باللغة العربية، منسقاً بالإيموجيات والعناوين والنقاط الواضحة والهاشتاغات المناسبة عن الموضوع التالي: +${promptTopic}`; + + const geminiRes = await fetch( + `https://generativelanguage.googleapis.com/v1beta/models/${process.env.GEMINI_MODEL || 'gemini-2.0-flash'}:generateContent?key=${process.env.GEMINI_API_KEY}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ contents: [{ parts: [{ text: systemPrompt }] }] }), + }, + ); + + const geminiData: any = await geminiRes.json(); + const postContent = geminiData?.candidates?.[0]?.content?.parts?.[0]?.text?.trim() || promptTopic; + + const endpoint = imageUrl ? `${pageId}/photos` : `${pageId}/feed`; + const metaUrl = `https://graph.facebook.com/v19.0/${endpoint}`; + const payload = imageUrl + ? { access_token: accessToken, caption: postContent, url: imageUrl } + : { access_token: accessToken, message: postContent }; + + const fbRes = await fetch(metaUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + + const fbData: any = await fbRes.json(); + if (fbData.error) { + throw new Error(`Meta Graph API Error [${fbData.error.code}]: ${fbData.error.message}`); + } + + return { ok: true, postId: fbData.id || fbData.post_id, postContent }; + } + async runCampaign(campaignId: string, userIds: string[]): Promise<{ sent: number }> { const campaign = await this.campaignRepo.findOne({ where: { id: campaignId } }); if (!campaign) throw new Error('Campaign not found'); diff --git a/scripts/ai-post-generator.js b/scripts/ai-post-generator.js index 8d19ed2..bd0cc35 100644 --- a/scripts/ai-post-generator.js +++ b/scripts/ai-post-generator.js @@ -81,19 +81,22 @@ async function generatePostWithGemini(topic = 'منشور تعريفي عن من } /** - * Publish generated post to Meta Facebook Page + * Publish generated post to Meta Facebook Page (supports optional image) */ -async function publishToFacebook(message) { +async function publishToFacebook(message, imageUrl = null) { console.log(`Publishing generated post to Facebook Page ID: ${META_PAGE_ID}...`); - const url = `https://graph.facebook.com/v19.0/${META_PAGE_ID}/feed`; + const endpoint = imageUrl ? `${META_PAGE_ID}/photos` : `${META_PAGE_ID}/feed`; + const url = `https://graph.facebook.com/v19.0/${endpoint}`; + + const payload = imageUrl + ? { access_token: META_ACCESS_TOKEN, caption: message, url: imageUrl } + : { access_token: META_ACCESS_TOKEN, message: message }; + const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - access_token: META_ACCESS_TOKEN, - message: message - }) + body: JSON.stringify(payload) }); const data = await res.json(); @@ -107,15 +110,21 @@ async function publishToFacebook(message) { async function main() { const topic = process.argv[2] || 'منشور تعريفي عن منصة Tripz ومزايا التملك والشفافية والتوفير وحاسبة الأرباح'; + const imageUrl = process.argv[3] || null; + try { const postContent = await generatePostWithGemini(topic); console.log('\n--- Generated Post Content ---\n'); console.log(postContent); console.log('\n------------------------------\n'); - const result = await publishToFacebook(postContent); + if (imageUrl) { + console.log('Attached Image Creative URL:', imageUrl); + } + + const result = await publishToFacebook(postContent, imageUrl); console.log('🎉 Post Published Successfully to Facebook!'); - console.log('Meta Post ID:', result.id); + console.log('Meta Post ID:', result.id || result.post_id); } catch (err) { console.error('❌ Error:', err.message); }