별책부록 7편
⚖️ Position C 5차원 강화
베팅·픽 자동 검출 + /about·/terms·/privacy 명령
📑 이 챕터에서 다룰 내용
3·4편의 봇 코드에 ⚖️ Position C footer만 적용했다면, 이 편에서는 5차원 완전 적용으로 올려 드립니다. footer + 시스템 프롬프트 + 자동 검출 + /about·/terms·/privacy 명령까지, 시리즈 메타 원칙 4차원 일관의 봇 적용이에요.
📘 7편 개요
- 예상 시간: 1시간
- 핵심: 베팅·픽·금융 단정 표현 자동 차단 + 시리즈 4차원 일관
- 대상 봇: 디스코드 봇 (TypeScript) + Telegram 봇 (Python)
시리즈 본문 vs 봇 차원 비교
| 차원 | 시리즈 본문 (권3) | 봇 적용 |
|---|---|---|
| 1. About 페이지 | About 본문 | /about 명령 |
| 2. 약관 §3 | 변호 논리 | /terms 명령 |
| 3. 개인정보 §1 | 익명 집계 | /privacy 명령 |
| 4. Footer | 모든 페이지 끝 | 모든 응답 끝 ✅ (3·4편 적용) |
| 5. 자동 검출 | ⭐ E5 [9] | ★ 본 편 추가 |
💡 봇 = 4차원 ⇒ 5차원 강화
3·4편에서 적용한 footer + 시스템 프롬프트 + /about·/terms·/privacy 명령(4차원)에, 이 편에서 자동 검출(5차원)을 추가합니다.
자동 검출이 추가되면 베팅·픽·금융 단정 표현이 사용자 질문에 담기거나 봇 응답에 나타날 경우 자동으로 차단됩니다.
검출 모듈
💻 src/safety_filter.ts — ⚖️ Position C 자동 검출
// src/safety_filter.ts
// ⚖️ Position C 자동 검출
interface SafetyResult {
safe: boolean;
reason?: string;
matched_keywords?: string[];
}
// 베팅·픽 키워드 풀
const GAMBLING_KEYWORDS = [
'베팅', '배팅', '도박',
'픽', '추천픽', '오늘의픽', '확정픽',
'배당', '배당률',
'odds', 'bet', 'pick',
'사다리', '파워볼', '카지노',
'먹튀', '먹튀검증', '안전놀이터',
'토토', '스포츠토토', '프로토',
'꽁머니', '환전',
];
// 단정 표현 키워드 풀
const ABSOLUTE_KEYWORDS_HIGH_RISK = [
'확실히', '100%', '무조건',
'절대', '반드시 이긴', '반드시 진',
'오를 것', '내릴 것',
'확실한 수익', '대박',
];
// 법률·의료·금융 단정
const PROFESSIONAL_KEYWORDS = [
'약 처방', '복용량 ',
'법적 책임', '소송',
'주식 매수', '주식 매도', '코인 매수', '코인 매도',
];
export function checkUserMessage(text: string): SafetyResult {
const lower = text.toLowerCase();
const matched: string[] = [];
// 1. 베팅·픽 검출
for (const kw of GAMBLING_KEYWORDS) {
if (lower.includes(kw.toLowerCase())) matched.push(kw);
}
if (matched.length > 0) {
return {
safe: false,
reason: 'gambling',
matched_keywords: matched,
};
}
return { safe: true };
}
export function checkAssistantResponse(text: string): SafetyResult {
const matched: string[] = [];
// 1. 베팅·픽 (응답에 절대 X)
for (const kw of GAMBLING_KEYWORDS) {
if (text.includes(kw)) matched.push(kw);
}
if (matched.length > 0) {
return {
safe: false,
reason: 'gambling_in_response',
matched_keywords: matched,
};
}
// 2. 단정 표현 high risk
let highRiskCount = 0;
for (const kw of ABSOLUTE_KEYWORDS_HIGH_RISK) {
if (text.includes(kw)) {
matched.push(kw);
highRiskCount++;
}
}
if (highRiskCount >= 2) {
return {
safe: false,
reason: 'absolute_assertion',
matched_keywords: matched,
};
}
return { safe: true };
}
봇 코드에 통합
💻 src/bot.ts — 안전 필터 통합
// src/bot.ts (수정)
import { checkUserMessage, checkAssistantResponse } from './safety_filter.js';
// messageCreate 핸들러 안
client.on('messageCreate', async (msg: Message) => {
if (msg.author.bot) return;
if (!msg.content.startsWith(COMMAND_PREFIX)) return;
const question = msg.content.slice(COMMAND_PREFIX.length).trim();
if (!question) return;
// ★ 1. 사용자 질문 검출
const userCheck = checkUserMessage(question);
if (!userCheck.safe && userCheck.reason === 'gambling') {
await msg.reply(
'죄송합니다. 베팅·도박 관련 질문에는 답변드릴 수 없습니다.\n' +
'본 봇은 분석 콘텐츠 보조 도구이며, 베팅·픽 추천을 제공하지 않습니다.'
);
logTrace({
timestamp: new Date().toISOString(),
user_id: msg.author.id,
channel_id: msg.channel.id,
question,
response_length: 0,
duration_ms: 0,
status: 'blocked_user', // ★ 새 status
});
return;
}
// ... (기존 askClaude 호출)
try {
const response = await askClaude(question);
// ★ 2. 응답 검출
const responseCheck = checkAssistantResponse(response);
if (!responseCheck.safe) {
// ⭐ E5 [9] 자동 검출 fail
logError(new Error('Position C violation in response'), {
question,
response,
check: responseCheck,
});
await msg.reply(
'⚠️ 적절한 답변을 생성하지 못했습니다. 질문을 다시 해주세요.'
);
return;
}
// 정상 응답
const fullResponse = `${response}\n\n${POSITION_C_FOOTER}`;
await msg.reply(fullResponse);
} catch (err: any) {
// ... (기존 에러 처리)
}
});
💻 src/commands.ts — 슬래시 명령 등록 및 핸들러
// src/commands.ts
import {
REST,
Routes,
SlashCommandBuilder,
Client,
ChatInputCommandInteraction
} from 'discord.js';
export const slashCommands = [
new SlashCommandBuilder()
.setName('about')
.setDescription('이 봇에 대해 알아보기'),
new SlashCommandBuilder()
.setName('terms')
.setDescription('이용 약관'),
new SlashCommandBuilder()
.setName('privacy')
.setDescription('개인정보 처리'),
].map(c => c.toJSON());
export async function registerSlashCommands(): Promise<void> {
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN!);
await rest.put(
Routes.applicationCommands(process.env.DISCORD_APP_ID!),
{ body: slashCommands }
);
console.log('✅ Slash commands registered');
}
export async function handleSlashCommand(
interaction: ChatInputCommandInteraction
): Promise<void> {
const { commandName } = interaction;
if (commandName === 'about') {
await interaction.reply(getAboutText());
} else if (commandName === 'terms') {
await interaction.reply(getTermsText());
} else if (commandName === 'privacy') {
await interaction.reply(getPrivacyText());
}
}
function getAboutText(): string {
return `
**TSV Assistant Bot — About**
본 봇은 TSV (TotalSportsView) 운영자의 보조 도구입니다.
[Promise 5]
1. 분석 콘텐츠 작성 보조
2. 19리그 데이터 검색
3. 코드 작성 + 디버깅 보조
4. 일반적 질문 답변
5. ⚖️ Position C 본문 일관 적용
[Don't Promise 5]
1. ❌ 베팅·픽 추천 제공
2. ❌ 법률·의료·금융 단정 조언
3. ❌ 실시간 경기 결과 (API 의존)
4. ❌ 개인 정보 저장 (LogOnTable 만)
5. ❌ 24/7 100% 응답 보장
문의: tsv-bot@example.com
`.trim();
}
function getTermsText(): string {
return `
**TSV Assistant Bot — Terms of Service**
§1. 본 봇은 분석 콘텐츠 보조 도구입니다.
§2. 사용자는 본 봇의 답변을 참고용으로만 사용해야 합니다.
§3. ⚖️ 본 봇은 베팅·픽 추천 시장에 사용 X.
본 봇의 답변을 베팅·도박 사이트 인용 시 즉시 사용 중지.
§4. 위반 사용자는 차단될 수 있습니다.
§5. 본 약관은 사전 통지 없이 변경될 수 있습니다.
`.trim();
}
function getPrivacyText(): string {
return `
**TSV Assistant Bot — Privacy**
§1. 본 봇은 익명 집계 LogOnTable 만 저장:
- 사용자 ID (Discord/Telegram 식별자)
- 질문 내용 (운영 점검용)
- 응답 길이·시간·상태
§2. 본 봇은 다음을 저장 X:
- 사용자 실명·전화번호·이메일
- 사용자 위치 정보
- 사용자 결제 정보
§3. 로그는 7일 후 자동 삭제 (logrotate).
§4. 운영자가 로그를 검토할 수 있으나 외부 공유 X.
§5. 데이터 삭제 요청: tsv-bot@example.com
`.trim();
}
봇 코드 통합
💻 src/bot.ts — 슬래시 명령 핸들러 등록
// src/bot.ts (수정)
import {
registerSlashCommands,
handleSlashCommand
} from './commands.js';
client.once('ready', async () => {
console.log(`✅ Bot ready: ${client.user?.tag}`);
await registerSlashCommands();
});
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return;
await handleSlashCommand(interaction);
});
💻 src/safety_filter.py — ⚖️ Position C 자동 검출 (Python)
# src/safety_filter.py
"""
⚖️ Position C 자동 검출 (Python)
"""
from typing import Optional
GAMBLING_KEYWORDS = [
"베팅", "배팅", "도박",
"픽", "추천픽", "오늘의픽", "확정픽",
"배당", "배당률",
"odds", "bet", "pick",
"사다리", "파워볼", "카지노",
"먹튀", "먹튀검증", "안전놀이터",
"토토", "스포츠토토", "프로토",
"꽁머니", "환전",
]
ABSOLUTE_KEYWORDS_HIGH_RISK = [
"확실히", "100%", "무조건",
"절대", "반드시 이긴", "반드시 진",
"오를 것", "내릴 것",
"확실한 수익", "대박",
]
def check_user_message(text: str) -> tuple[bool, Optional[str]]:
lower = text.lower()
for kw in GAMBLING_KEYWORDS:
if kw.lower() in lower:
return False, "gambling"
return True, None
def check_assistant_response(text: str) -> tuple[bool, Optional[str]]:
matched = []
for kw in GAMBLING_KEYWORDS:
if kw in text:
matched.append(kw)
if matched:
return False, "gambling_in_response"
high_risk_count = sum(1 for kw in ABSOLUTE_KEYWORDS_HIGH_RISK if kw in text)
if high_risk_count >= 2:
return False, "absolute_assertion"
return True, None
💻 src/bot.py — /about·/terms·/privacy 명령 + 안전 필터 통합
# src/bot.py (수정)
from safety_filter import check_user_message, check_assistant_response
async def about_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(
"TSV Assistant Bot - About\n\n"
"본 봇은 TSV 운영자의 보조 도구입니다.\n\n"
"[Promise 5]\n"
"1. 분석 콘텐츠 작성 보조\n"
"2. 19리그 데이터 검색\n"
"3. 코드 작성·디버깅\n"
"4. 일반 질문 답변\n"
"5. Position C 일관\n\n"
"[Don't Promise]\n"
"1. 베팅·픽 추천 X\n"
"2. 법률·의료·금융 단정 X"
)
async def terms_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(
"TSV Bot - Terms\n\n"
"§1. 분석 콘텐츠 보조 도구\n"
"§2. 답변은 참고용\n"
"§3. 베팅·픽 시장 사용 X\n"
"§4. 위반 시 차단\n"
"§5. 약관 변경 가능"
)
async def privacy_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(
"TSV Bot - Privacy\n\n"
"§1. 익명 집계만 저장\n"
"§2. 실명·전화·이메일 X\n"
"§3. 7일 후 자동 삭제\n"
"§4. 외부 공유 X\n"
"§5. 삭제 요청: tsv-bot@example.com"
)
# main() 안에 핸들러 추가
app.add_handler(CommandHandler("about", about_command))
app.add_handler(CommandHandler("terms", terms_command))
app.add_handler(CommandHandler("privacy", privacy_command))
# /ask 안에 검출 추가
async def ask_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
question = " ".join(context.args).strip()
if not question:
await update.message.reply_text(ERROR_MESSAGES["no_question"])
return
# ★ 사용자 검출
safe, reason = check_user_message(question)
if not safe and reason == "gambling":
await update.message.reply_text(
"죄송합니다. 베팅·도박 관련 질문에는 답변드릴 수 없습니다.\n"
"본 봇은 분석 콘텐츠 보조 도구이며, 베팅·픽 추천을 제공하지 않습니다."
)
return
# ... (기존 흐름)
# ★ 응답 검출
response = await ask_claude(question)
safe, reason = check_assistant_response(response)
if not safe:
await update.message.reply_text(
"⚠️ 적절한 답변을 생성하지 못했습니다. 다시 시도해주세요."
)
log_error(Exception("Position C violation"), {"question": question})
return
# ... (정상 응답)
💻 차단 로그 분석 (jq 사용)
# 검출 빈도 확인 (jq 사용)
sudo apt install -y jq
# 차단된 사용자 질문 (gambling)
cat ~/tsv-bot/logs/$(date +%Y-%m-%d).jsonl | \
jq -r 'select(.status == "blocked_user") | .question'
# 차단된 응답 (Position C 위반)
cat ~/tsv-bot/logs/$(date +%Y-%m-%d)-errors.jsonl | \
jq -r 'select(.error == "Position C violation in response") | .context.question'
# 일별 차단 통계
cat ~/tsv-bot/logs/*.jsonl | \
jq -r 'select(.status | startswith("blocked")) | .timestamp[:10]' | \
sort | uniq -c
💡 차단 로그를 정기적으로 확인하세요
차단 로그를 주 1회 확인하면 어떤 질문 유형이 들어오는지 파악할 수 있어요.
베팅 관련 질문이 잦다면 키워드 풀을 확장하고, 오탐(정상 질문이 차단됨)이 있다면 키워드를 조정하세요.
7편 — Sentry + 매일 리포트에서 이 로그를 자동으로 집계해 이메일로 받을 수 있어요.
📌 7편 정리
- 1️⃣ 5차원 완전 적용: footer + SYSTEM_PROMPT + 사용자 검출 + 응답 검출 + /about·/terms·/privacy
- 2️⃣ 사용자 질문 자동 검출: 베팅·픽 키워드 → 즉시 차단 + LogOnTable 기록
- 3️⃣ 응답 자동 검출: 봇 응답에 gambling 키워드 또는 단정 표현 2개 이상 → 차단
- 4️⃣ /about·/terms·/privacy: 디스코드 슬래시 명령 + Telegram 명령 동일 적용
- 5️⃣ Node.js + Python 동시 적용: safety_filter.ts / safety_filter.py 분리 모듈화
- 6️⃣ 차단 로그 분석: jq로 일별 통계 → 키워드 풀 지속 개선
📘 5차원 전체 구조 정리
- footer (3·4편 적용)
- SYSTEM_PROMPT (3·4편 적용)
- ★ 사용자 질문 자동 검출 (본 편)
- ★ 응답 자동 검출 (본 편)
- ★ /about·/terms·/privacy 명령 (본 편)
시리즈 본문 4차원 + 봇 자동 검출 = 메타 진화 완료
🎉 핵심 한 줄
⚖️ Position C 봇 5차원 일관 — footer + system prompt + 자동 검출 user + 자동 검출 response + /about·/terms·/privacy
다음 편: 08편 — Sentry + 매일 리포트 (운영 모니터링 + Budget Alert)