feat(bot): studiofromis_9 채널 봇 추가 및 cron 표현식 지원

- .env에 YouTube API 키 추가
- bots 테이블에 channel_id, rss_url, cron_expression 컬럼 추가
- youtube-scheduler.js: cron_expression 파라미터 지원
- youtube-bot.js: bots 테이블에서 직접 조회, Shorts 필터링 제거
- studiofromis_9 봇 등록 (채널: UCW0ORl7mZO8wIWkgtq-yzjQ, cron: 1,2 * * * *)
This commit is contained in:
caadiq 2026-01-05 22:26:44 +09:00
parent 1b01182028
commit e216539f34
3 changed files with 28 additions and 35 deletions

3
.env
View file

@ -19,3 +19,6 @@ RUSTFS_BUCKET=fromis-9
# Kakao API # Kakao API
KAKAO_REST_KEY=cf21156ae6cc8f6e95b3a3150926cdf8 KAKAO_REST_KEY=cf21156ae6cc8f6e95b3a3150926cdf8
# YouTube API
YOUTUBE_API_KEY=AIzaSyC6l3nFlcHgLc0d1Q9WPyYQjVKTv21ZqFs

View file

@ -241,20 +241,19 @@ export async function createScheduleFromVideo(video, categoryId) {
*/ */
export async function syncNewVideos(botId) { export async function syncNewVideos(botId) {
try { try {
// 봇 정보 조회 // 봇 정보 조회 (bots 테이블에서 직접)
const [bots] = await pool.query( const [bots] = await pool.query(`SELECT * FROM bots WHERE id = ?`, [botId]);
`SELECT b.*, c.channel_id, c.rss_url, c.include_shorts
FROM bots b
JOIN bot_youtube_config c ON b.id = c.bot_id
WHERE b.id = ?`,
[botId]
);
if (bots.length === 0) { if (bots.length === 0) {
throw new Error("봇을 찾을 수 없습니다."); throw new Error("봇을 찾을 수 없습니다.");
} }
const bot = bots[0]; const bot = bots[0];
if (!bot.rss_url) {
throw new Error("RSS URL이 설정되지 않았습니다.");
}
const categoryId = await getYoutubeCategory(); const categoryId = await getYoutubeCategory();
// RSS 피드 파싱 // RSS 피드 파싱
@ -262,10 +261,7 @@ export async function syncNewVideos(botId) {
let addedCount = 0; let addedCount = 0;
for (const video of videos) { for (const video of videos) {
// Shorts 필터링 // Shorts도 포함하여 일정으로 추가
if (!bot.include_shorts && video.videoType === "shorts") {
continue;
}
const scheduleId = await createScheduleFromVideo(video, categoryId); const scheduleId = await createScheduleFromVideo(video, categoryId);
if (scheduleId) { if (scheduleId) {
@ -303,20 +299,19 @@ export async function syncNewVideos(botId) {
*/ */
export async function syncAllVideos(botId) { export async function syncAllVideos(botId) {
try { try {
// 봇 정보 조회 // 봇 정보 조회 (bots 테이블에서 직접)
const [bots] = await pool.query( const [bots] = await pool.query(`SELECT * FROM bots WHERE id = ?`, [botId]);
`SELECT b.*, c.channel_id, c.include_shorts
FROM bots b
JOIN bot_youtube_config c ON b.id = c.bot_id
WHERE b.id = ?`,
[botId]
);
if (bots.length === 0) { if (bots.length === 0) {
throw new Error("봇을 찾을 수 없습니다."); throw new Error("봇을 찾을 수 없습니다.");
} }
const bot = bots[0]; const bot = bots[0];
if (!bot.channel_id) {
throw new Error("Channel ID가 설정되지 않았습니다.");
}
const categoryId = await getYoutubeCategory(); const categoryId = await getYoutubeCategory();
// API로 전체 영상 수집 // API로 전체 영상 수집
@ -324,10 +319,7 @@ export async function syncAllVideos(botId) {
let addedCount = 0; let addedCount = 0;
for (const video of videos) { for (const video of videos) {
// Shorts 필터링 // Shorts도 포함하여 일정으로 추가
if (!bot.include_shorts && video.videoType === "shorts") {
continue;
}
const scheduleId = await createScheduleFromVideo(video, categoryId); const scheduleId = await createScheduleFromVideo(video, categoryId);
if (scheduleId) { if (scheduleId) {

View file

@ -8,14 +8,14 @@ const schedulers = new Map();
/** /**
* 개별 스케줄 등록 * 개별 스케줄 등록
*/ */
export function registerBot(botId, intervalMinutes = 2) { export function registerBot(botId, intervalMinutes = 2, cronExpression = null) {
// 기존 스케줄이 있으면 제거 // 기존 스케줄이 있으면 제거
unregisterBot(botId); unregisterBot(botId);
// cron 표현식 생성 (1분 시작, X분 간격: 1,3,5,7...) // cron 표현식: 지정된 표현식 사용, 없으면 기본값 생성
const cronExpression = `1-59/${intervalMinutes} * * * *`; const expression = cronExpression || `1-59/${intervalMinutes} * * * *`;
const task = cron.schedule(cronExpression, async () => { const task = cron.schedule(expression, async () => {
console.log(`[Bot ${botId}] 동기화 시작...`); console.log(`[Bot ${botId}] 동기화 시작...`);
try { try {
const result = await syncNewVideos(botId); const result = await syncNewVideos(botId);
@ -26,9 +26,7 @@ export function registerBot(botId, intervalMinutes = 2) {
}); });
schedulers.set(botId, task); schedulers.set(botId, task);
console.log( console.log(`[Bot ${botId}] 스케줄 등록됨 (cron: ${expression})`);
`[Bot ${botId}] 스케줄 등록됨 (${intervalMinutes}분 간격, 1분 오프셋)`
);
} }
/** /**
@ -48,11 +46,11 @@ export function unregisterBot(botId) {
export async function initScheduler() { export async function initScheduler() {
try { try {
const [bots] = await pool.query( const [bots] = await pool.query(
"SELECT id, check_interval FROM bots WHERE status = 'running'" "SELECT id, check_interval, cron_expression FROM bots WHERE status = 'running'"
); );
for (const bot of bots) { for (const bot of bots) {
registerBot(bot.id, bot.check_interval); registerBot(bot.id, bot.check_interval, bot.cron_expression);
} }
console.log(`[Scheduler] ${bots.length}개 봇 스케줄 등록됨`); console.log(`[Scheduler] ${bots.length}개 봇 스케줄 등록됨`);
@ -72,8 +70,8 @@ export async function startBot(botId) {
const bot = bots[0]; const bot = bots[0];
// 스케줄 등록 // 스케줄 등록 (cron_expression 우선 사용)
registerBot(botId, bot.check_interval); registerBot(botId, bot.check_interval, bot.cron_expression);
// 상태 업데이트 // 상태 업데이트
await pool.query( await pool.query(