Add NestJS Admin Facebook Publishing API with Image Support
This commit is contained in:
@@ -88,6 +88,14 @@ export class MarketingController {
|
|||||||
return this.marketingService.generateContent(user.tenantId, body.prompt);
|
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')
|
@Delete('campaigns/:id')
|
||||||
@HttpCode(HttpStatus.NO_CONTENT)
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
async deleteCampaign(@Param('id') id: string) {
|
async deleteCampaign(@Param('id') id: string) {
|
||||||
|
|||||||
@@ -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 }> {
|
async runCampaign(campaignId: string, userIds: string[]): Promise<{ sent: number }> {
|
||||||
const campaign = await this.campaignRepo.findOne({ where: { id: campaignId } });
|
const campaign = await this.campaignRepo.findOne({ where: { id: campaignId } });
|
||||||
if (!campaign) throw new Error('Campaign not found');
|
if (!campaign) throw new Error('Campaign not found');
|
||||||
|
|||||||
@@ -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}...`);
|
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, {
|
const res = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(payload)
|
||||||
access_token: META_ACCESS_TOKEN,
|
|
||||||
message: message
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
@@ -107,15 +110,21 @@ async function publishToFacebook(message) {
|
|||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const topic = process.argv[2] || 'منشور تعريفي عن منصة Tripz ومزايا التملك والشفافية والتوفير وحاسبة الأرباح';
|
const topic = process.argv[2] || 'منشور تعريفي عن منصة Tripz ومزايا التملك والشفافية والتوفير وحاسبة الأرباح';
|
||||||
|
const imageUrl = process.argv[3] || null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const postContent = await generatePostWithGemini(topic);
|
const postContent = await generatePostWithGemini(topic);
|
||||||
console.log('\n--- Generated Post Content ---\n');
|
console.log('\n--- Generated Post Content ---\n');
|
||||||
console.log(postContent);
|
console.log(postContent);
|
||||||
console.log('\n------------------------------\n');
|
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('🎉 Post Published Successfully to Facebook!');
|
||||||
console.log('Meta Post ID:', result.id);
|
console.log('Meta Post ID:', result.id || result.post_id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('❌ Error:', err.message);
|
console.error('❌ Error:', err.message);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user