별책부록 7편
별책부록 7편
⚖️ Position C 5차원 강화
베팅·픽 자동 검출 + /about·/terms·/privacy 명령
📑 이 챕터에서 다룰 내용

3·4편의 봇 코드에 ⚖️ Position C footer만 적용했다면, 이 편에서는 5차원 완전 적용으로 올려 드립니다. footer + 시스템 프롬프트 + 자동 검출 + /about·/terms·/privacy 명령까지, 시리즈 메타 원칙 4차원 일관의 봇 적용이에요.

📘 7편 개요
  • 예상 시간: 1시간
  • 핵심: 베팅·픽·금융 단정 표현 자동 차단 + 시리즈 4차원 일관
  • 대상 봇: 디스코드 봇 (TypeScript) + Telegram 봇 (Python)
6-1 ⚖️ Position C 4차원 → 5차원 봇 적용 🔗

시리즈 본문 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차원)을 추가합니다.

자동 검출이 추가되면 베팅·픽·금융 단정 표현이 사용자 질문에 담기거나 봇 응답에 나타날 경우 자동으로 차단됩니다.

6-2 베팅·픽 자동 검출 (Node.js) 🔗

검출 모듈

💻 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) {
    // ... (기존 에러 처리)
  }
});
6-3 /about, /terms, /privacy 명령 (디스코드 슬래시 명령) 🔗
💻 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);
});
6-4 Telegram 봇 적용 (Python) 🔗
💻 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

    # ... (정상 응답)
6-5 자동 검출 데이터 분석 🔗
💻 차단 로그 분석 (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차원 전체 구조 정리
  1. footer (3·4편 적용)
  2. SYSTEM_PROMPT (3·4편 적용)
  3. ★ 사용자 질문 자동 검출 (본 편)
  4. ★ 응답 자동 검출 (본 편)
  5. ★ /about·/terms·/privacy 명령 (본 편)

시리즈 본문 4차원 + 봇 자동 검출 = 메타 진화 완료

🎉 핵심 한 줄

⚖️ Position C 봇 5차원 일관 — footer + system prompt + 자동 검출 user + 자동 검출 response + /about·/terms·/privacy

다음 편: 08편 — Sentry + 매일 리포트 (운영 모니터링 + Budget Alert)

📘
별책부록 도우미
질문하기 OK
안녕하세요! Position C 5차원 강화에 대해 무엇이든 물어보세요. 본문에서 찾아 답변해드릴게요. 👇