castad-pre-v0.3/castad-data/server/services/autoUploadService.js

312 lines
9.1 KiB
JavaScript

/**
* Auto Upload Service
*
* Beginner/Intermediate 레벨 사용자의 영상 자동 업로드를 처리합니다.
* - YouTube, Instagram, TikTok에 자동 업로드
* - SEO 데이터 자동 생성 (Gemini 사용)
*/
const db = require('../db');
const path = require('path');
/**
* 사용자 레벨에 따른 자동 업로드 설정 확인
*/
const shouldAutoUpload = async (userId) => {
return new Promise((resolve, reject) => {
const query = `
SELECT u.experience_level, ags.auto_youtube, ags.auto_instagram, ags.auto_tiktok
FROM users u
LEFT JOIN auto_generation_settings ags ON u.id = ags.user_id
WHERE u.id = ?
`;
db.get(query, [userId], (err, row) => {
if (err) {
console.error('자동 업로드 설정 조회 실패:', err);
return resolve({ shouldUpload: false, platforms: {} });
}
if (!row) {
return resolve({ shouldUpload: false, platforms: {} });
}
const level = row.experience_level || 'beginner';
// Beginner/Intermediate는 기본 자동 업로드
if (level === 'beginner' || level === 'intermediate') {
return resolve({
shouldUpload: true,
platforms: {
youtube: true,
instagram: true,
tiktok: true
},
level
});
}
// Pro는 사용자 설정에 따름
if (level === 'pro') {
return resolve({
shouldUpload: row.auto_youtube || row.auto_instagram || row.auto_tiktok,
platforms: {
youtube: Boolean(row.auto_youtube),
instagram: Boolean(row.auto_instagram),
tiktok: Boolean(row.auto_tiktok)
},
level
});
}
resolve({ shouldUpload: false, platforms: {} });
});
});
};
/**
* SEO 데이터 자동 생성 (Gemini 사용)
*/
const generateAutoSeo = async (businessInfo, language = 'KO') => {
const { GoogleGenerativeAI } = require('@google/generative-ai');
const genAI = new GoogleGenerativeAI(process.env.VITE_GEMINI_API_KEY);
const langPrompts = {
KO: {
title: '한국어로',
tags: '한국어 해시태그',
},
EN: {
title: 'in English',
tags: 'English hashtags',
},
JA: {
title: '日本語で',
tags: '日本語ハッシュタグ',
},
ZH: {
title: '用中文',
tags: '中文标签',
},
TH: {
title: 'ในภาษาไทย',
tags: 'แฮชแท็กภาษาไทย',
},
VI: {
title: 'bằng tiếng Việt',
tags: 'hashtag tiếng Việt',
}
};
const langConfig = langPrompts[language] || langPrompts.KO;
const prompt = `
당신은 펜션/숙소 홍보 영상의 SEO 전문가입니다.
아래 정보를 바탕으로 YouTube, Instagram, TikTok용 SEO 데이터를 생성하세요.
펜션 정보:
- 이름: ${businessInfo.name || '펜션'}
- 설명: ${businessInfo.description || ''}
- 주소: ${businessInfo.address || ''}
- 카테고리: ${businessInfo.categories?.join(', ') || ''}
요구사항:
1. title: 매력적인 제목 (${langConfig.title}, 60자 이내)
2. description: 상세 설명 (${langConfig.title}, 200자 이내)
3. tags: 검색용 태그 10개 (${langConfig.tags}, 쉼표로 구분)
4. hashtags: 소셜미디어용 해시태그 15개 (${langConfig.tags}, #포함)
JSON 형식으로 응답하세요:
{
"title": "제목",
"description": "설명",
"tags": ["태그1", "태그2", ...],
"hashtags": "#태그1 #태그2 ..."
}
`;
try {
const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' });
const result = await model.generateContent(prompt);
const text = result.response.text();
// JSON 추출
const jsonMatch = text.match(/\{[\s\S]*\}/);
if (jsonMatch) {
return JSON.parse(jsonMatch[0]);
}
// 기본값 반환
return {
title: `${businessInfo.name || '펜션'} - 힐링 여행`,
description: businessInfo.description || '아름다운 펜션에서 특별한 휴식을 경험하세요.',
tags: ['펜션', '여행', '힐링', '휴식', '숙소'],
hashtags: '#펜션 #여행 #힐링 #휴식 #숙소 #vacation #travel'
};
} catch (error) {
console.error('SEO 자동 생성 실패:', error);
return {
title: `${businessInfo.name || '펜션'} - 힐링 여행`,
description: businessInfo.description || '아름다운 펜션에서 특별한 휴식을 경험하세요.',
tags: ['펜션', '여행', '힐링', '휴식', '숙소'],
hashtags: '#펜션 #여행 #힐링 #휴식 #숙소 #vacation #travel'
};
}
};
/**
* 자동 업로드 실행
*/
const executeAutoUpload = async (userId, videoPath, businessInfo, language = 'KO') => {
const results = {
youtube: { success: false, error: null },
instagram: { success: false, error: null },
tiktok: { success: false, error: null }
};
try {
// 자동 업로드 설정 확인
const uploadConfig = await shouldAutoUpload(userId);
if (!uploadConfig.shouldUpload) {
console.log('자동 업로드 비활성화 - 사용자 설정에 따름');
return { skipped: true, reason: '자동 업로드 비활성화됨', results };
}
// SEO 데이터 자동 생성
const seoData = await generateAutoSeo(businessInfo, language);
console.log('SEO 데이터 생성 완료:', seoData.title);
// YouTube 업로드
if (uploadConfig.platforms.youtube) {
try {
const youtubeService = require('../youtubeService');
const youtubeResult = await youtubeService.uploadVideo(
userId,
videoPath,
seoData.title,
seoData.description + '\n\n' + seoData.hashtags,
seoData.tags
);
results.youtube = { success: true, videoId: youtubeResult.videoId };
console.log('YouTube 업로드 성공:', youtubeResult.videoId);
} catch (ytError) {
console.error('YouTube 업로드 실패:', ytError.message);
results.youtube = { success: false, error: ytError.message };
}
}
// Instagram 업로드
if (uploadConfig.platforms.instagram) {
try {
// Instagram은 비디오를 릴스로 업로드
const response = await fetch('http://localhost:5001/upload-reel', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: await getInstagramUsername(userId),
video_path: videoPath,
caption: seoData.description + '\n\n' + seoData.hashtags
})
});
if (response.ok) {
const result = await response.json();
results.instagram = { success: true, mediaId: result.media_id };
console.log('Instagram 업로드 성공');
} else {
const errorData = await response.json();
results.instagram = { success: false, error: errorData.detail || '업로드 실패' };
}
} catch (igError) {
console.error('Instagram 업로드 실패:', igError.message);
results.instagram = { success: false, error: igError.message };
}
}
// TikTok 업로드
if (uploadConfig.platforms.tiktok) {
try {
const tiktokService = require('../tiktokService');
const tiktokResult = await tiktokService.uploadVideo(
userId,
videoPath,
seoData.description + ' ' + seoData.hashtags
);
results.tiktok = { success: true, videoId: tiktokResult.videoId };
console.log('TikTok 업로드 성공');
} catch (ttError) {
console.error('TikTok 업로드 실패:', ttError.message);
results.tiktok = { success: false, error: ttError.message };
}
}
return {
skipped: false,
level: uploadConfig.level,
seoData,
results
};
} catch (error) {
console.error('자동 업로드 실행 중 오류:', error);
return {
skipped: false,
error: error.message,
results
};
}
};
/**
* 사용자의 Instagram 계정명 가져오기
*/
const getInstagramUsername = async (userId) => {
return new Promise((resolve, reject) => {
db.get(
'SELECT instagram_username FROM social_connections WHERE user_id = ?',
[userId],
(err, row) => {
if (err || !row) {
resolve(null);
} else {
resolve(row.instagram_username);
}
}
);
});
};
/**
* 업로드 이력 저장
*/
const saveUploadHistory = async (userId, historyId, platform, result) => {
return new Promise((resolve, reject) => {
const query = `
INSERT INTO upload_history (user_id, history_id, platform, status, external_id, error_message, uploaded_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
`;
db.run(query, [
userId,
historyId,
platform,
result.success ? 'success' : 'failed',
result.videoId || result.mediaId || null,
result.error || null
], (err) => {
if (err) {
console.error('업로드 이력 저장 실패:', err);
}
resolve();
});
});
};
module.exports = {
shouldAutoUpload,
generateAutoSeo,
executeAutoUpload,
saveUploadHistory
};