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,
|
||||
extract_members_from_desc TINYINT(1) DEFAULT 0,
|
||||
extract_members_from_title TINYINT(1) DEFAULT 0,
|
||||
|
||||
-- 다음 주 예정 일정 설정 (JSON)
|
||||
auto_schedule_config JSON,
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ async function schedulerPlugin(fastify, opts) {
|
|||
: row.default_member_ids)
|
||||
: [],
|
||||
extractMembersFromDesc: row.extract_members_from_desc === 1,
|
||||
extractMembersFromTitle: row.extract_members_from_title === 1,
|
||||
autoScheduleNext: row.auto_schedule_config
|
||||
? (typeof row.auto_schedule_config === 'string'
|
||||
? JSON.parse(row.auto_schedule_config)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ const youtubeBotResponse = {
|
|||
title_filters: { type: 'array', items: { type: 'string' } },
|
||||
default_member_ids: { type: 'array', items: { type: 'integer' } },
|
||||
extract_members_from_desc: { type: 'boolean' },
|
||||
extract_members_from_title: { type: 'boolean' },
|
||||
auto_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)
|
||||
: [],
|
||||
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
|
||||
? (typeof row.auto_schedule_config === 'string'
|
||||
? JSON.parse(row.auto_schedule_config)
|
||||
|
|
@ -186,6 +188,7 @@ export default async function youtubeBotsRoutes(fastify) {
|
|||
title_filters: { type: ['array', 'null'], items: { type: 'string' } },
|
||||
default_member_ids: { type: ['array', 'null'], items: { type: 'integer' } },
|
||||
extract_members_from_desc: { type: 'boolean', default: false },
|
||||
extract_members_from_title: { type: 'boolean', default: false },
|
||||
auto_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,
|
||||
default_member_ids,
|
||||
extract_members_from_desc = false,
|
||||
extract_members_from_title = false,
|
||||
auto_schedule_config,
|
||||
weekly_schedule_config,
|
||||
} = request.body;
|
||||
|
|
@ -226,9 +230,9 @@ export default async function youtubeBotsRoutes(fastify) {
|
|||
const [result] = await db.query(
|
||||
`INSERT INTO bot_youtube
|
||||
(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)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`,
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`,
|
||||
[
|
||||
channel_id,
|
||||
channel_handle || null,
|
||||
|
|
@ -238,6 +242,7 @@ export default async function youtubeBotsRoutes(fastify) {
|
|||
title_filters ? JSON.stringify(title_filters) : null,
|
||||
default_member_ids ? JSON.stringify(default_member_ids) : null,
|
||||
extract_members_from_desc ? 1 : 0,
|
||||
extract_members_from_title ? 1 : 0,
|
||||
auto_schedule_config ? JSON.stringify(auto_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' } },
|
||||
default_member_ids: { type: ['array', 'null'], items: { type: 'integer' } },
|
||||
extract_members_from_desc: { type: 'boolean' },
|
||||
extract_members_from_title: { type: 'boolean' },
|
||||
auto_schedule_config: { type: ['object', 'null'], additionalProperties: true },
|
||||
weekly_schedule_config: { type: ['object', 'null'], additionalProperties: true },
|
||||
enabled: { type: 'boolean' },
|
||||
|
|
@ -331,6 +337,10 @@ export default async function youtubeBotsRoutes(fastify) {
|
|||
fields.push('extract_members_from_desc = ?');
|
||||
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) {
|
||||
fields.push('auto_schedule_config = ?');
|
||||
values.push(updates.auto_schedule_config ? JSON.stringify(updates.auto_schedule_config) : null);
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@ async function youtubeBotPlugin(fastify) {
|
|||
|
||||
// 멤버 이름 맵 미리 조회 (트랜잭션 전에)
|
||||
let nameMap = null;
|
||||
if (bot.extractMembersFromDesc) {
|
||||
if (bot.extractMembersFromDesc || bot.extractMembersFromTitle) {
|
||||
nameMap = await getMemberNameMap();
|
||||
}
|
||||
|
||||
|
|
@ -295,14 +295,17 @@ async function youtubeBotPlugin(fastify) {
|
|||
|
||||
// 멤버 연결 (커스텀 설정)
|
||||
const hasDefaultMembers = bot.defaultMemberIds && bot.defaultMemberIds.length > 0;
|
||||
if (hasDefaultMembers || bot.extractMembersFromDesc) {
|
||||
if (hasDefaultMembers || bot.extractMembersFromDesc || bot.extractMembersFromTitle) {
|
||||
const memberIds = [];
|
||||
if (hasDefaultMembers) {
|
||||
memberIds.push(...bot.defaultMemberIds);
|
||||
}
|
||||
if (nameMap) {
|
||||
if (nameMap && bot.extractMembersFromDesc) {
|
||||
memberIds.push(...extractMemberIds(video.description, nameMap));
|
||||
}
|
||||
if (nameMap && bot.extractMembersFromTitle) {
|
||||
memberIds.push(...extractMemberIds(video.title, nameMap));
|
||||
}
|
||||
if (memberIds.length > 0) {
|
||||
const uniqueIds = [...new Set(memberIds)];
|
||||
const values = uniqueIds.map(id => [newScheduleId, id]);
|
||||
|
|
|
|||
|
|
@ -331,6 +331,7 @@ YouTube 봇 추가
|
|||
"title_filters": ["fromis_9", "프로미스나인"],
|
||||
"default_member_ids": [1, 2],
|
||||
"extract_members_from_desc": true,
|
||||
"extract_members_from_title": false,
|
||||
"auto_schedule_config": {
|
||||
"dayOfWeek": 4,
|
||||
"time": "18:00:00",
|
||||
|
|
|
|||
|
|
@ -300,6 +300,7 @@ function YouTubeBotDialog({ isOpen, onClose, botId = null, onSuccess }) {
|
|||
const [filterInput, setFilterInput] = useState('');
|
||||
const [defaultMemberIds, setDefaultMemberIds] = useState([]);
|
||||
const [extractMembers, setExtractMembers] = useState(false);
|
||||
const [extractMembersFromTitle, setExtractMembersFromTitle] = useState(false);
|
||||
|
||||
// 멤버 목록 (탈퇴 멤버 제외)
|
||||
const [members, setMembers] = useState([]);
|
||||
|
|
@ -381,11 +382,13 @@ function YouTubeBotDialog({ isOpen, onClose, botId = null, onSuccess }) {
|
|||
setTitleFilters(bot.title_filters || []);
|
||||
setDefaultMemberIds(bot.default_member_ids || []);
|
||||
setExtractMembers(bot.extract_members_from_desc || false);
|
||||
setExtractMembersFromTitle(bot.extract_members_from_title || false);
|
||||
|
||||
// 고급 설정이 있으면 펼침
|
||||
if ((bot.title_filters && bot.title_filters.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);
|
||||
} else {
|
||||
setShowAdvanced(false);
|
||||
|
|
@ -410,6 +413,7 @@ function YouTubeBotDialog({ isOpen, onClose, botId = null, onSuccess }) {
|
|||
setFilterInput('');
|
||||
setDefaultMemberIds([]);
|
||||
setExtractMembers(false);
|
||||
setExtractMembersFromTitle(false);
|
||||
}
|
||||
}, [isOpen, bot, botId]);
|
||||
|
||||
|
|
@ -447,6 +451,7 @@ function YouTubeBotDialog({ isOpen, onClose, botId = null, onSuccess }) {
|
|||
title_filters: titleFilters.length > 0 ? titleFilters : null,
|
||||
default_member_ids: defaultMemberIds.length > 0 ? defaultMemberIds : null,
|
||||
extract_members_from_desc: extractMembers,
|
||||
extract_members_from_title: extractMembersFromTitle,
|
||||
auto_schedule_config: autoScheduleEnabled
|
||||
? {
|
||||
dayOfWeek: scheduleDayOfWeek,
|
||||
|
|
@ -848,6 +853,28 @@ function YouTubeBotDialog({ isOpen, onClose, botId = null, onSuccess }) {
|
|||
/>
|
||||
</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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue