feat(youtube-bot): 제목에서 멤버 추출 옵션 추가
- bot_youtube에 extract_members_from_title 컬럼 추가 (기본값 0) - services/youtube/index.js: 설명과 제목에서 각각 멤버 이름 검색, 합집합으로 중복 제거 - YouTubeBotDialog 고급 설정에 토글 추가 (설명 추출 토글 아래) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
678e228bc5
commit
bcc8555193
6 changed files with 49 additions and 6 deletions
|
|
@ -14,6 +14,7 @@ CREATE TABLE IF NOT EXISTS bot_youtube (
|
||||||
-- 멤버 설정 (선택)
|
-- 멤버 설정 (선택)
|
||||||
default_member_ids JSON,
|
default_member_ids JSON,
|
||||||
extract_members_from_desc TINYINT(1) DEFAULT 0,
|
extract_members_from_desc TINYINT(1) DEFAULT 0,
|
||||||
|
extract_members_from_title TINYINT(1) DEFAULT 0,
|
||||||
|
|
||||||
-- 다음 주 예정 일정 설정 (JSON)
|
-- 다음 주 예정 일정 설정 (JSON)
|
||||||
auto_schedule_config JSON,
|
auto_schedule_config JSON,
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ async function schedulerPlugin(fastify, opts) {
|
||||||
: row.default_member_ids)
|
: row.default_member_ids)
|
||||||
: [],
|
: [],
|
||||||
extractMembersFromDesc: row.extract_members_from_desc === 1,
|
extractMembersFromDesc: row.extract_members_from_desc === 1,
|
||||||
|
extractMembersFromTitle: row.extract_members_from_title === 1,
|
||||||
autoScheduleNext: row.auto_schedule_config
|
autoScheduleNext: row.auto_schedule_config
|
||||||
? (typeof row.auto_schedule_config === 'string'
|
? (typeof row.auto_schedule_config === 'string'
|
||||||
? JSON.parse(row.auto_schedule_config)
|
? JSON.parse(row.auto_schedule_config)
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ const youtubeBotResponse = {
|
||||||
title_filters: { type: 'array', items: { type: 'string' } },
|
title_filters: { type: 'array', items: { type: 'string' } },
|
||||||
default_member_ids: { type: 'array', items: { type: 'integer' } },
|
default_member_ids: { type: 'array', items: { type: 'integer' } },
|
||||||
extract_members_from_desc: { type: 'boolean' },
|
extract_members_from_desc: { type: 'boolean' },
|
||||||
|
extract_members_from_title: { type: 'boolean' },
|
||||||
auto_schedule_config: { type: ['object', 'null'], additionalProperties: true },
|
auto_schedule_config: { type: ['object', 'null'], additionalProperties: true },
|
||||||
weekly_schedule_config: { type: ['object', 'null'], additionalProperties: true },
|
weekly_schedule_config: { type: ['object', 'null'], additionalProperties: true },
|
||||||
},
|
},
|
||||||
|
|
@ -55,6 +56,7 @@ function formatBotResponse(row) {
|
||||||
: row.default_member_ids)
|
: row.default_member_ids)
|
||||||
: [],
|
: [],
|
||||||
extract_members_from_desc: row.extract_members_from_desc === 1,
|
extract_members_from_desc: row.extract_members_from_desc === 1,
|
||||||
|
extract_members_from_title: row.extract_members_from_title === 1,
|
||||||
auto_schedule_config: row.auto_schedule_config
|
auto_schedule_config: row.auto_schedule_config
|
||||||
? (typeof row.auto_schedule_config === 'string'
|
? (typeof row.auto_schedule_config === 'string'
|
||||||
? JSON.parse(row.auto_schedule_config)
|
? JSON.parse(row.auto_schedule_config)
|
||||||
|
|
@ -186,6 +188,7 @@ export default async function youtubeBotsRoutes(fastify) {
|
||||||
title_filters: { type: ['array', 'null'], items: { type: 'string' } },
|
title_filters: { type: ['array', 'null'], items: { type: 'string' } },
|
||||||
default_member_ids: { type: ['array', 'null'], items: { type: 'integer' } },
|
default_member_ids: { type: ['array', 'null'], items: { type: 'integer' } },
|
||||||
extract_members_from_desc: { type: 'boolean', default: false },
|
extract_members_from_desc: { type: 'boolean', default: false },
|
||||||
|
extract_members_from_title: { type: 'boolean', default: false },
|
||||||
auto_schedule_config: { type: ['object', 'null'], additionalProperties: true },
|
auto_schedule_config: { type: ['object', 'null'], additionalProperties: true },
|
||||||
weekly_schedule_config: { type: ['object', 'null'], additionalProperties: true },
|
weekly_schedule_config: { type: ['object', 'null'], additionalProperties: true },
|
||||||
},
|
},
|
||||||
|
|
@ -207,6 +210,7 @@ export default async function youtubeBotsRoutes(fastify) {
|
||||||
title_filters,
|
title_filters,
|
||||||
default_member_ids,
|
default_member_ids,
|
||||||
extract_members_from_desc = false,
|
extract_members_from_desc = false,
|
||||||
|
extract_members_from_title = false,
|
||||||
auto_schedule_config,
|
auto_schedule_config,
|
||||||
weekly_schedule_config,
|
weekly_schedule_config,
|
||||||
} = request.body;
|
} = request.body;
|
||||||
|
|
@ -226,9 +230,9 @@ export default async function youtubeBotsRoutes(fastify) {
|
||||||
const [result] = await db.query(
|
const [result] = await db.query(
|
||||||
`INSERT INTO bot_youtube
|
`INSERT INTO bot_youtube
|
||||||
(channel_id, channel_handle, channel_name, banner_url, cron_interval,
|
(channel_id, channel_handle, channel_name, banner_url, cron_interval,
|
||||||
title_filters, default_member_ids, extract_members_from_desc,
|
title_filters, default_member_ids, extract_members_from_desc, extract_members_from_title,
|
||||||
auto_schedule_config, weekly_schedule_config, enabled)
|
auto_schedule_config, weekly_schedule_config, enabled)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`,
|
||||||
[
|
[
|
||||||
channel_id,
|
channel_id,
|
||||||
channel_handle || null,
|
channel_handle || null,
|
||||||
|
|
@ -238,6 +242,7 @@ export default async function youtubeBotsRoutes(fastify) {
|
||||||
title_filters ? JSON.stringify(title_filters) : null,
|
title_filters ? JSON.stringify(title_filters) : null,
|
||||||
default_member_ids ? JSON.stringify(default_member_ids) : null,
|
default_member_ids ? JSON.stringify(default_member_ids) : null,
|
||||||
extract_members_from_desc ? 1 : 0,
|
extract_members_from_desc ? 1 : 0,
|
||||||
|
extract_members_from_title ? 1 : 0,
|
||||||
auto_schedule_config ? JSON.stringify(auto_schedule_config) : null,
|
auto_schedule_config ? JSON.stringify(auto_schedule_config) : null,
|
||||||
weekly_schedule_config ? JSON.stringify(weekly_schedule_config) : null,
|
weekly_schedule_config ? JSON.stringify(weekly_schedule_config) : null,
|
||||||
]
|
]
|
||||||
|
|
@ -278,6 +283,7 @@ export default async function youtubeBotsRoutes(fastify) {
|
||||||
title_filters: { type: ['array', 'null'], items: { type: 'string' } },
|
title_filters: { type: ['array', 'null'], items: { type: 'string' } },
|
||||||
default_member_ids: { type: ['array', 'null'], items: { type: 'integer' } },
|
default_member_ids: { type: ['array', 'null'], items: { type: 'integer' } },
|
||||||
extract_members_from_desc: { type: 'boolean' },
|
extract_members_from_desc: { type: 'boolean' },
|
||||||
|
extract_members_from_title: { type: 'boolean' },
|
||||||
auto_schedule_config: { type: ['object', 'null'], additionalProperties: true },
|
auto_schedule_config: { type: ['object', 'null'], additionalProperties: true },
|
||||||
weekly_schedule_config: { type: ['object', 'null'], additionalProperties: true },
|
weekly_schedule_config: { type: ['object', 'null'], additionalProperties: true },
|
||||||
enabled: { type: 'boolean' },
|
enabled: { type: 'boolean' },
|
||||||
|
|
@ -331,6 +337,10 @@ export default async function youtubeBotsRoutes(fastify) {
|
||||||
fields.push('extract_members_from_desc = ?');
|
fields.push('extract_members_from_desc = ?');
|
||||||
values.push(updates.extract_members_from_desc ? 1 : 0);
|
values.push(updates.extract_members_from_desc ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
if (updates.extract_members_from_title !== undefined) {
|
||||||
|
fields.push('extract_members_from_title = ?');
|
||||||
|
values.push(updates.extract_members_from_title ? 1 : 0);
|
||||||
|
}
|
||||||
if (updates.auto_schedule_config !== undefined) {
|
if (updates.auto_schedule_config !== undefined) {
|
||||||
fields.push('auto_schedule_config = ?');
|
fields.push('auto_schedule_config = ?');
|
||||||
values.push(updates.auto_schedule_config ? JSON.stringify(updates.auto_schedule_config) : null);
|
values.push(updates.auto_schedule_config ? JSON.stringify(updates.auto_schedule_config) : null);
|
||||||
|
|
|
||||||
|
|
@ -272,7 +272,7 @@ async function youtubeBotPlugin(fastify) {
|
||||||
|
|
||||||
// 멤버 이름 맵 미리 조회 (트랜잭션 전에)
|
// 멤버 이름 맵 미리 조회 (트랜잭션 전에)
|
||||||
let nameMap = null;
|
let nameMap = null;
|
||||||
if (bot.extractMembersFromDesc) {
|
if (bot.extractMembersFromDesc || bot.extractMembersFromTitle) {
|
||||||
nameMap = await getMemberNameMap();
|
nameMap = await getMemberNameMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -295,14 +295,17 @@ async function youtubeBotPlugin(fastify) {
|
||||||
|
|
||||||
// 멤버 연결 (커스텀 설정)
|
// 멤버 연결 (커스텀 설정)
|
||||||
const hasDefaultMembers = bot.defaultMemberIds && bot.defaultMemberIds.length > 0;
|
const hasDefaultMembers = bot.defaultMemberIds && bot.defaultMemberIds.length > 0;
|
||||||
if (hasDefaultMembers || bot.extractMembersFromDesc) {
|
if (hasDefaultMembers || bot.extractMembersFromDesc || bot.extractMembersFromTitle) {
|
||||||
const memberIds = [];
|
const memberIds = [];
|
||||||
if (hasDefaultMembers) {
|
if (hasDefaultMembers) {
|
||||||
memberIds.push(...bot.defaultMemberIds);
|
memberIds.push(...bot.defaultMemberIds);
|
||||||
}
|
}
|
||||||
if (nameMap) {
|
if (nameMap && bot.extractMembersFromDesc) {
|
||||||
memberIds.push(...extractMemberIds(video.description, nameMap));
|
memberIds.push(...extractMemberIds(video.description, nameMap));
|
||||||
}
|
}
|
||||||
|
if (nameMap && bot.extractMembersFromTitle) {
|
||||||
|
memberIds.push(...extractMemberIds(video.title, nameMap));
|
||||||
|
}
|
||||||
if (memberIds.length > 0) {
|
if (memberIds.length > 0) {
|
||||||
const uniqueIds = [...new Set(memberIds)];
|
const uniqueIds = [...new Set(memberIds)];
|
||||||
const values = uniqueIds.map(id => [newScheduleId, id]);
|
const values = uniqueIds.map(id => [newScheduleId, id]);
|
||||||
|
|
|
||||||
|
|
@ -331,6 +331,7 @@ YouTube 봇 추가
|
||||||
"title_filters": ["fromis_9", "프로미스나인"],
|
"title_filters": ["fromis_9", "프로미스나인"],
|
||||||
"default_member_ids": [1, 2],
|
"default_member_ids": [1, 2],
|
||||||
"extract_members_from_desc": true,
|
"extract_members_from_desc": true,
|
||||||
|
"extract_members_from_title": false,
|
||||||
"auto_schedule_config": {
|
"auto_schedule_config": {
|
||||||
"dayOfWeek": 4,
|
"dayOfWeek": 4,
|
||||||
"time": "18:00:00",
|
"time": "18:00:00",
|
||||||
|
|
|
||||||
|
|
@ -300,6 +300,7 @@ function YouTubeBotDialog({ isOpen, onClose, botId = null, onSuccess }) {
|
||||||
const [filterInput, setFilterInput] = useState('');
|
const [filterInput, setFilterInput] = useState('');
|
||||||
const [defaultMemberIds, setDefaultMemberIds] = useState([]);
|
const [defaultMemberIds, setDefaultMemberIds] = useState([]);
|
||||||
const [extractMembers, setExtractMembers] = useState(false);
|
const [extractMembers, setExtractMembers] = useState(false);
|
||||||
|
const [extractMembersFromTitle, setExtractMembersFromTitle] = useState(false);
|
||||||
|
|
||||||
// 멤버 목록 (탈퇴 멤버 제외)
|
// 멤버 목록 (탈퇴 멤버 제외)
|
||||||
const [members, setMembers] = useState([]);
|
const [members, setMembers] = useState([]);
|
||||||
|
|
@ -381,11 +382,13 @@ function YouTubeBotDialog({ isOpen, onClose, botId = null, onSuccess }) {
|
||||||
setTitleFilters(bot.title_filters || []);
|
setTitleFilters(bot.title_filters || []);
|
||||||
setDefaultMemberIds(bot.default_member_ids || []);
|
setDefaultMemberIds(bot.default_member_ids || []);
|
||||||
setExtractMembers(bot.extract_members_from_desc || false);
|
setExtractMembers(bot.extract_members_from_desc || false);
|
||||||
|
setExtractMembersFromTitle(bot.extract_members_from_title || false);
|
||||||
|
|
||||||
// 고급 설정이 있으면 펼침
|
// 고급 설정이 있으면 펼침
|
||||||
if ((bot.title_filters && bot.title_filters.length > 0) ||
|
if ((bot.title_filters && bot.title_filters.length > 0) ||
|
||||||
(bot.default_member_ids && bot.default_member_ids.length > 0) ||
|
(bot.default_member_ids && bot.default_member_ids.length > 0) ||
|
||||||
bot.extract_members_from_desc) {
|
bot.extract_members_from_desc ||
|
||||||
|
bot.extract_members_from_title) {
|
||||||
setShowAdvanced(true);
|
setShowAdvanced(true);
|
||||||
} else {
|
} else {
|
||||||
setShowAdvanced(false);
|
setShowAdvanced(false);
|
||||||
|
|
@ -410,6 +413,7 @@ function YouTubeBotDialog({ isOpen, onClose, botId = null, onSuccess }) {
|
||||||
setFilterInput('');
|
setFilterInput('');
|
||||||
setDefaultMemberIds([]);
|
setDefaultMemberIds([]);
|
||||||
setExtractMembers(false);
|
setExtractMembers(false);
|
||||||
|
setExtractMembersFromTitle(false);
|
||||||
}
|
}
|
||||||
}, [isOpen, bot, botId]);
|
}, [isOpen, bot, botId]);
|
||||||
|
|
||||||
|
|
@ -447,6 +451,7 @@ function YouTubeBotDialog({ isOpen, onClose, botId = null, onSuccess }) {
|
||||||
title_filters: titleFilters.length > 0 ? titleFilters : null,
|
title_filters: titleFilters.length > 0 ? titleFilters : null,
|
||||||
default_member_ids: defaultMemberIds.length > 0 ? defaultMemberIds : null,
|
default_member_ids: defaultMemberIds.length > 0 ? defaultMemberIds : null,
|
||||||
extract_members_from_desc: extractMembers,
|
extract_members_from_desc: extractMembers,
|
||||||
|
extract_members_from_title: extractMembersFromTitle,
|
||||||
auto_schedule_config: autoScheduleEnabled
|
auto_schedule_config: autoScheduleEnabled
|
||||||
? {
|
? {
|
||||||
dayOfWeek: scheduleDayOfWeek,
|
dayOfWeek: scheduleDayOfWeek,
|
||||||
|
|
@ -848,6 +853,28 @@ function YouTubeBotDialog({ isOpen, onClose, botId = null, onSuccess }) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 제목에서 멤버 추출 */}
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-between cursor-pointer"
|
||||||
|
onClick={() => setExtractMembersFromTitle(!extractMembersFromTitle)}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-gray-700">제목에서 멤버 추출</p>
|
||||||
|
<p className="text-xs text-gray-500">영상 제목에서 멤버 이름을 찾아 자동 연결</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`w-10 h-5 rounded-full transition-colors ${
|
||||||
|
extractMembersFromTitle ? 'bg-red-500' : 'bg-gray-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`w-4 h-4 bg-white rounded-full shadow-sm transform transition-transform mt-0.5 ${
|
||||||
|
extractMembersFromTitle ? 'translate-x-5' : 'translate-x-0.5'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue