AI generated first iteration
This commit is contained in:
1
app/agent/__init__.py
Normal file
1
app/agent/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Agent module exports."""
|
||||
1
app/agent/modes/__init__.py
Normal file
1
app/agent/modes/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Agent modes module."""
|
||||
47
app/agent/modes/hearthkeeper.py
Normal file
47
app/agent/modes/hearthkeeper.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""Hearthkeeper Mode - Nurtures stream warmth and gentle presence."""
|
||||
|
||||
import logging
|
||||
from app.llm.client import LLMClient
|
||||
from app.llm.prompts import PromptTemplates
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HearthkeeperMode:
|
||||
"""
|
||||
Hearthkeeper - The gentle voice of the Sanctum.
|
||||
|
||||
Purpose:
|
||||
- Maintains the emotional warmth of the stream
|
||||
- Generates gentle prompts when chat is quiet
|
||||
- Encourages participation and connection
|
||||
- Never forced or aggressive
|
||||
|
||||
Policy:
|
||||
- Activates when no human chat for 15+ minutes
|
||||
- Generates 1-2 sentence conversation starters
|
||||
- Respects stream theme if established
|
||||
- Can be suppressed if chat becomes active
|
||||
"""
|
||||
|
||||
def __init__(self, llm_client: LLMClient):
|
||||
"""Initialize Hearthkeeper mode."""
|
||||
self.llm_client = llm_client
|
||||
self.last_activity_minutes = 0
|
||||
self.activity_threshold = 15
|
||||
|
||||
async def should_activate(self, minutes_since_activity: int) -> bool:
|
||||
"""Determine if Hearthkeeper should generate a prompt."""
|
||||
return minutes_since_activity >= self.activity_threshold
|
||||
|
||||
async def generate_prompt(self, theme: str | None = None) -> str:
|
||||
"""Generate a gentle prompt for the stream."""
|
||||
prompt = PromptTemplates.gentle_prompt(theme)
|
||||
response = await self.llm_client.generate(prompt)
|
||||
logger.info("Hearthkeeper generated gentle prompt")
|
||||
return response
|
||||
|
||||
async def on_chat_activity(self) -> None:
|
||||
"""React to new chat activity."""
|
||||
logger.debug("Hearthkeeper notes renewed activity")
|
||||
self.last_activity_minutes = 0
|
||||
64
app/agent/modes/librarian.py
Normal file
64
app/agent/modes/librarian.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""Librarian Mode - Archives and categorizes important discussion."""
|
||||
|
||||
import logging
|
||||
from app.llm.client import LLMClient
|
||||
from app.llm.prompts import PromptTemplates
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LibrarianMode:
|
||||
"""
|
||||
Librarian - The keeper of knowledge and archives.
|
||||
|
||||
Purpose:
|
||||
- Identifies and catalogs important discussion points
|
||||
- Creates summaries of key topics
|
||||
- Builds context for future reference
|
||||
- Prepares data for blog and clip exports
|
||||
|
||||
Policy:
|
||||
- Runs passively, always monitoring
|
||||
- Tags messages by topic/sentiment
|
||||
- Creates discussion threads
|
||||
- Identifies "clip-worthy" moments
|
||||
- Feeds data to Scribe for final export
|
||||
"""
|
||||
|
||||
def __init__(self, llm_client: LLMClient):
|
||||
"""Initialize Librarian mode."""
|
||||
self.llm_client = llm_client
|
||||
self.archived_messages: list[dict] = []
|
||||
self.topics: dict[str, list[str]] = {}
|
||||
|
||||
async def archive_message(self, message_id: str, content: str, username: str) -> None:
|
||||
"""Archive an important message."""
|
||||
self.archived_messages.append(
|
||||
{
|
||||
"id": message_id,
|
||||
"content": content,
|
||||
"username": username,
|
||||
}
|
||||
)
|
||||
logger.debug(f"Librarian archived message from {username}")
|
||||
|
||||
async def identify_topics(self, messages: list[str]) -> list[str]:
|
||||
"""Identify key topics from a set of messages."""
|
||||
# Placeholder: Would use LLM to extract topics
|
||||
topics = ["general", "technical", "community"]
|
||||
return topics
|
||||
|
||||
async def create_summary(self, topic: str, messages: list[str]) -> str:
|
||||
"""Create a summary of messages under a topic."""
|
||||
prompt = PromptTemplates.librarian_summary(messages)
|
||||
summary = await self.llm_client.generate(prompt, max_tokens=300)
|
||||
logger.info(f"Librarian created summary for topic: {topic}")
|
||||
return summary
|
||||
|
||||
async def get_archives(self) -> dict:
|
||||
"""Get the archive status."""
|
||||
return {
|
||||
"mode": "librarian",
|
||||
"archived_messages": len(self.archived_messages),
|
||||
"topics_tracked": len(self.topics),
|
||||
}
|
||||
73
app/agent/modes/scribe.py
Normal file
73
app/agent/modes/scribe.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""Scribe Mode - Generates the post-stream markdown ledger."""
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from app.llm.client import LLMClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ScribeMode:
|
||||
"""
|
||||
Scribe - The chronicler of the stream's story.
|
||||
|
||||
Purpose:
|
||||
- Compiles session data into a markdown ledger
|
||||
- Generates blog post seeds
|
||||
- Identifies clip candidates
|
||||
- Exports final summary document
|
||||
|
||||
Policy:
|
||||
- Activates at end of stream
|
||||
- Reads data from Librarian, Warden, and repository
|
||||
- Creates structured markdown output
|
||||
- Organizes clips and blog topics
|
||||
- Ready for post-processing or publishing
|
||||
"""
|
||||
|
||||
def __init__(self, llm_client: LLMClient):
|
||||
"""Initialize Scribe mode."""
|
||||
self.llm_client = llm_client
|
||||
self.ledger_entries: list[str] = []
|
||||
|
||||
async def add_entry(self, section: str, content: str) -> None:
|
||||
"""Add an entry to the ledger."""
|
||||
self.ledger_entries.append(f"## {section}\n{content}")
|
||||
logger.debug(f"Scribe recorded ledger entry: {section}")
|
||||
|
||||
async def compile_ledger(
|
||||
self,
|
||||
theme: str,
|
||||
discussion_points: list[str],
|
||||
agent_actions: list[str],
|
||||
clip_candidates: list[str],
|
||||
blog_seeds: list[str],
|
||||
) -> str:
|
||||
"""Compile all data into a markdown ledger."""
|
||||
date = datetime.utcnow().strftime("%Y-%m-%d")
|
||||
|
||||
ledger = f"# Sanctum Ledger — {date}\n\n"
|
||||
ledger += f"## Stream Theme\n{theme}\n\n"
|
||||
ledger += f"## Notable Discussion\n"
|
||||
for point in discussion_points:
|
||||
ledger += f"- {point}\n"
|
||||
ledger += "\n"
|
||||
ledger += f"## Agent Actions\n"
|
||||
for action in agent_actions:
|
||||
ledger += f"- {action}\n"
|
||||
ledger += "\n"
|
||||
ledger += f"## Clip Candidates\n"
|
||||
for clip in clip_candidates:
|
||||
ledger += f"- {clip}\n"
|
||||
ledger += "\n"
|
||||
ledger += f"## Blog Seeds\n"
|
||||
for seed in blog_seeds:
|
||||
ledger += f"- {seed}\n"
|
||||
|
||||
logger.info("Scribe compiled stream ledger")
|
||||
return ledger
|
||||
|
||||
async def export_ledger(self, filename: str, content: str) -> None:
|
||||
"""Export ledger to file."""
|
||||
# Actual export handled by MarkdownExporter
|
||||
logger.info(f"Scribe prepared ledger for export: {filename}")
|
||||
51
app/agent/modes/steward.py
Normal file
51
app/agent/modes/steward.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""Steward Mode - Responds to chat with knowledge and warmth."""
|
||||
|
||||
import logging
|
||||
from app.llm.client import LLMClient
|
||||
from app.llm.prompts import PromptTemplates
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StewardMode:
|
||||
"""
|
||||
Steward - The thoughtful keeper of conversation.
|
||||
|
||||
Purpose:
|
||||
- Responds to direct questions and comments
|
||||
- Shares relevant knowledge and context
|
||||
- Maintains conversation continuity
|
||||
- Balances speaking and listening
|
||||
|
||||
Policy:
|
||||
- Activates when chat is active
|
||||
- Only responds to messages explicitly mentioning the bot
|
||||
- Keeps responses brief (1-3 sentences)
|
||||
- Never interrupts human conversation flow
|
||||
- Can escalate to other modes if needed
|
||||
"""
|
||||
|
||||
def __init__(self, llm_client: LLMClient):
|
||||
"""Initialize Steward mode."""
|
||||
self.llm_client = llm_client
|
||||
self.response_count = 0
|
||||
self.max_responses_per_minute = 2
|
||||
|
||||
async def should_respond(self, message: str, is_mention: bool) -> bool:
|
||||
"""Determine if Steward should respond."""
|
||||
# Only respond to mentions for now (can be expanded)
|
||||
return is_mention and self.response_count < self.max_responses_per_minute
|
||||
|
||||
async def generate_response(
|
||||
self, message: str, context: str | None = None
|
||||
) -> str:
|
||||
"""Generate a thoughtful response to a message."""
|
||||
prompt = PromptTemplates.steward_response(message, context)
|
||||
response = await self.llm_client.generate(prompt, max_tokens=150)
|
||||
self.response_count += 1
|
||||
logger.info("Steward generated response")
|
||||
return response
|
||||
|
||||
async def on_response_sent(self) -> None:
|
||||
"""Record that a response was sent."""
|
||||
logger.debug("Steward response recorded")
|
||||
85
app/agent/modes/warden.py
Normal file
85
app/agent/modes/warden.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""Warden Mode - Detects and flags suspicious content."""
|
||||
|
||||
import logging
|
||||
from app.llm.client import LLMClient
|
||||
from app.llm.prompts import PromptTemplates
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WardenMode:
|
||||
"""
|
||||
Warden - The guardian against unwanted influences.
|
||||
|
||||
Purpose:
|
||||
- Detects suspicious patterns (spam, scams, bot activity)
|
||||
- Flags Discord growth schemes and link spam
|
||||
- Monitors for manipulation or harmful content
|
||||
- Provides data for moderation decisions
|
||||
|
||||
Policy:
|
||||
- Runs on every message (always active)
|
||||
- Never takes action directly (only flags)
|
||||
- Patterns to detect:
|
||||
* "Join our Discord"
|
||||
* "Grow your channel"
|
||||
* Multiple links
|
||||
* Repeated messages (spam)
|
||||
* Known scam keywords
|
||||
- Flags are recorded for human review
|
||||
"""
|
||||
|
||||
def __init__(self, llm_client: LLMClient):
|
||||
"""Initialize Warden mode."""
|
||||
self.llm_client = llm_client
|
||||
self.suspicious_patterns = [
|
||||
"join our discord",
|
||||
"discord.gg",
|
||||
"grow your channel",
|
||||
"easy money",
|
||||
"click here",
|
||||
"limited offer",
|
||||
"act now",
|
||||
]
|
||||
self.flagged_count = 0
|
||||
|
||||
async def analyze_message(self, message: str) -> dict:
|
||||
"""Analyze a message for suspicious content."""
|
||||
result = {
|
||||
"is_suspicious": False,
|
||||
"patterns_detected": [],
|
||||
"severity": "safe",
|
||||
}
|
||||
|
||||
# Simple pattern matching
|
||||
message_lower = message.lower()
|
||||
for pattern in self.suspicious_patterns:
|
||||
if pattern in message_lower:
|
||||
result["patterns_detected"].append(pattern)
|
||||
result["is_suspicious"] = True
|
||||
|
||||
# Check for multiple links
|
||||
link_count = message.count("http") + message.count("www")
|
||||
if link_count > 1:
|
||||
result["patterns_detected"].append("multiple_links")
|
||||
result["is_suspicious"] = True
|
||||
|
||||
# Determine severity
|
||||
if result["is_suspicious"]:
|
||||
if len(result["patterns_detected"]) >= 2:
|
||||
result["severity"] = "high"
|
||||
else:
|
||||
result["severity"] = "medium"
|
||||
self.flagged_count += 1
|
||||
logger.warning(
|
||||
f"Warden flagged suspicious message: {result['patterns_detected']}"
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
async def get_report(self) -> dict:
|
||||
"""Get Warden's activity report."""
|
||||
return {
|
||||
"mode": "warden",
|
||||
"total_flagged": self.flagged_count,
|
||||
}
|
||||
211
app/agent/orchestrator.py
Normal file
211
app/agent/orchestrator.py
Normal file
@@ -0,0 +1,211 @@
|
||||
"""Agent Orchestrator - Routes messages and manages agent modes."""
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.agent.policies import (
|
||||
ChatActivityPolicy,
|
||||
ResponseSuppression,
|
||||
SuspiciousContentPolicy,
|
||||
)
|
||||
from app.agent.modes.hearthkeeper import HearthkeeperMode
|
||||
from app.agent.modes.steward import StewardMode
|
||||
from app.agent.modes.warden import WardenMode
|
||||
from app.agent.modes.librarian import LibrarianMode
|
||||
from app.agent.modes.scribe import ScribeMode
|
||||
from app.llm.client import LLMClient
|
||||
from app.memory.database import async_session_factory
|
||||
from app.memory.models import AgentActionType
|
||||
from app.memory.repository import Repository
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AgentOrchestrator:
|
||||
"""
|
||||
Main orchestrator for agent behavior.
|
||||
|
||||
Routes chat messages to appropriate modes and manages responses.
|
||||
Implements policies for when to speak, when to stay silent,
|
||||
and how to flag suspicious content.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the orchestrator and all modes."""
|
||||
self.llm_client = LLMClient()
|
||||
|
||||
# Initialize modes
|
||||
self.hearthkeeper = HearthkeeperMode(self.llm_client)
|
||||
self.steward = StewardMode(self.llm_client)
|
||||
self.warden = WardenMode(self.llm_client)
|
||||
self.librarian = LibrarianMode(self.llm_client)
|
||||
self.scribe = ScribeMode(self.llm_client)
|
||||
|
||||
# Initialize policies
|
||||
self.chat_activity = ChatActivityPolicy(inactivity_threshold_minutes=15)
|
||||
self.response_suppression = ResponseSuppression()
|
||||
self.suspicious_content = SuspiciousContentPolicy()
|
||||
|
||||
# Track active sessions
|
||||
self.active_sessions: dict[str, dict] = {}
|
||||
|
||||
logger.info("AgentOrchestrator initialized with all modes and policies")
|
||||
|
||||
async def start_session(self, channel_name: str) -> str:
|
||||
"""
|
||||
Start a new stream session.
|
||||
|
||||
Args:
|
||||
channel_name: Twitch channel name
|
||||
|
||||
Returns:
|
||||
Session ID
|
||||
"""
|
||||
session_id = str(uuid.uuid4())
|
||||
|
||||
async with async_session_factory() as db_session:
|
||||
repo = Repository(db_session)
|
||||
await repo.create_session(channel_name)
|
||||
|
||||
self.active_sessions[session_id] = {
|
||||
"channel_name": channel_name,
|
||||
"started_at": datetime.utcnow(),
|
||||
"message_count": 0,
|
||||
"theme": None,
|
||||
}
|
||||
|
||||
logger.info(f"Started session {session_id} for {channel_name}")
|
||||
return session_id
|
||||
|
||||
async def end_session(self, session_id: str) -> None:
|
||||
"""
|
||||
End a stream session and trigger ledger generation.
|
||||
|
||||
Args:
|
||||
session_id: Session ID
|
||||
"""
|
||||
if session_id not in self.active_sessions:
|
||||
logger.warning(f"Session {session_id} not found")
|
||||
return
|
||||
|
||||
async with async_session_factory() as db_session:
|
||||
repo = Repository(db_session)
|
||||
await repo.end_session(session_id)
|
||||
|
||||
del self.active_sessions[session_id]
|
||||
logger.info(f"Ended session {session_id}")
|
||||
|
||||
async def handle_chat_message(
|
||||
self,
|
||||
session_id: str,
|
||||
username: str,
|
||||
message: str,
|
||||
) -> dict:
|
||||
"""
|
||||
Process a chat message and determine agent response.
|
||||
|
||||
Args:
|
||||
session_id: Session ID
|
||||
username: Username of message sender
|
||||
message: Message content
|
||||
|
||||
Returns:
|
||||
Response dict with agent_response, actions_taken, etc.
|
||||
"""
|
||||
if session_id not in self.active_sessions:
|
||||
logger.warning(f"Session {session_id} not found")
|
||||
return {"agent_response": None, "actions_taken": []}
|
||||
|
||||
session_info = self.active_sessions[session_id]
|
||||
actions = []
|
||||
agent_response = None
|
||||
|
||||
async with async_session_factory() as db_session:
|
||||
repo = Repository(db_session)
|
||||
|
||||
# Store the message
|
||||
message_id = await repo.add_chat_message(
|
||||
session_id=session_id,
|
||||
username=username,
|
||||
content=message,
|
||||
is_bot=False,
|
||||
)
|
||||
|
||||
# Record activity
|
||||
self.chat_activity.record_activity(session_id)
|
||||
session_info["message_count"] += 1
|
||||
|
||||
# 1. Warden always analyzes (passive mode)
|
||||
warden_result = await self.warden.analyze_message(message)
|
||||
if warden_result["is_suspicious"]:
|
||||
actions.append(f"WARDEN_FLAG: {warden_result['severity']}")
|
||||
async with async_session_factory() as db_session:
|
||||
repo = Repository(db_session)
|
||||
await repo.record_action(
|
||||
session_id=session_id,
|
||||
action_type=AgentActionType.FLAG_SUSPICIOUS,
|
||||
mode="warden",
|
||||
description=f"Detected: {warden_result['patterns_detected']}",
|
||||
triggered_by_message_id=message_id,
|
||||
)
|
||||
|
||||
# 2. Check if we should suppress responses due to active chat
|
||||
recent_messages = []
|
||||
async with async_session_factory() as db_session:
|
||||
repo = Repository(db_session)
|
||||
recent_messages = await repo.get_recent_messages(session_id, limit=10)
|
||||
|
||||
if self.response_suppression.should_suppress_response(len(recent_messages)):
|
||||
logger.debug("Response suppressed due to active chat")
|
||||
return {
|
||||
"agent_response": agent_response,
|
||||
"actions_taken": actions,
|
||||
}
|
||||
|
||||
# 3. Hearthkeeper: Generate prompt if chat inactive
|
||||
if self.chat_activity.should_hearthkeeper_prompt(session_id):
|
||||
try:
|
||||
agent_response = await self.hearthkeeper.generate_prompt(
|
||||
theme=session_info.get("theme")
|
||||
)
|
||||
actions.append("HEARTHKEEPER_PROMPT")
|
||||
async with async_session_factory() as db_session:
|
||||
repo = Repository(db_session)
|
||||
await repo.record_action(
|
||||
session_id=session_id,
|
||||
action_type=AgentActionType.RESPONSE,
|
||||
mode="hearthkeeper",
|
||||
description=agent_response,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in Hearthkeeper: {e}")
|
||||
|
||||
# 4. Librarian: Archive important messages (passive)
|
||||
if len(message) > 50: # Archive longer messages
|
||||
await self.librarian.archive_message(message_id, message, username)
|
||||
|
||||
logger.info(
|
||||
f"Message processed. Session: {session_id}, Actions: {actions}"
|
||||
)
|
||||
|
||||
return {
|
||||
"agent_response": agent_response,
|
||||
"actions_taken": actions,
|
||||
}
|
||||
|
||||
async def get_session_status(self, session_id: str) -> dict:
|
||||
"""Get status of a session."""
|
||||
if session_id not in self.active_sessions:
|
||||
return {}
|
||||
|
||||
session = self.active_sessions[session_id]
|
||||
|
||||
return {
|
||||
"session_id": session_id,
|
||||
"channel_name": session["channel_name"],
|
||||
"message_count": session["message_count"],
|
||||
"uptime_seconds": (datetime.utcnow() - session["started_at"]).total_seconds(),
|
||||
"theme": session.get("theme"),
|
||||
}
|
||||
105
app/agent/policies.py
Normal file
105
app/agent/policies.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""Agent behavior policies and rules."""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ChatActivityPolicy:
|
||||
"""Policy for detecting chat activity and inactivity periods."""
|
||||
|
||||
def __init__(self, inactivity_threshold_minutes: int = 15):
|
||||
"""
|
||||
Initialize policy.
|
||||
|
||||
Args:
|
||||
inactivity_threshold_minutes: Minutes of no chat before Hearthkeeper activates
|
||||
"""
|
||||
self.inactivity_threshold = timedelta(minutes=inactivity_threshold_minutes)
|
||||
self.last_message_time: dict[str, datetime] = {}
|
||||
|
||||
def record_activity(self, session_id: str) -> None:
|
||||
"""Record that chat activity occurred."""
|
||||
self.last_message_time[session_id] = datetime.utcnow()
|
||||
|
||||
def minutes_since_activity(self, session_id: str) -> int:
|
||||
"""Get minutes since last chat message."""
|
||||
if session_id not in self.last_message_time:
|
||||
return 0
|
||||
elapsed = datetime.utcnow() - self.last_message_time[session_id]
|
||||
return int(elapsed.total_seconds() / 60)
|
||||
|
||||
def should_hearthkeeper_prompt(self, session_id: str) -> bool:
|
||||
"""Determine if Hearthkeeper should send a prompt."""
|
||||
minutes = self.minutes_since_activity(session_id)
|
||||
should = minutes >= self.inactivity_threshold.total_seconds() / 60
|
||||
if should:
|
||||
logger.info(f"Chat inactive for {minutes} minutes. Hearthkeeper may prompt.")
|
||||
return should
|
||||
|
||||
|
||||
class ResponseSuppression:
|
||||
"""Policy for when the agent should NOT respond."""
|
||||
|
||||
# Suppress responses when chat is very active (humans are talking)
|
||||
ACTIVE_CHAT_THRESHOLD = 5 # 5+ messages per minute = suppress
|
||||
|
||||
@staticmethod
|
||||
def should_suppress_response(recent_message_count: int, time_window_minutes: int = 1) -> bool:
|
||||
"""
|
||||
Determine if agent should stay silent due to active chat.
|
||||
|
||||
Args:
|
||||
recent_message_count: Number of messages in the time window
|
||||
time_window_minutes: Time window in minutes
|
||||
|
||||
Returns:
|
||||
True if agent should suppress response
|
||||
"""
|
||||
messages_per_minute = recent_message_count / time_window_minutes
|
||||
suppress = messages_per_minute >= ResponseSuppression.ACTIVE_CHAT_THRESHOLD
|
||||
|
||||
if suppress:
|
||||
logger.debug(f"Response suppressed due to active chat ({messages_per_minute:.1f} msg/min)")
|
||||
|
||||
return suppress
|
||||
|
||||
|
||||
class SuspiciousContentPolicy:
|
||||
"""Policy for detecting suspicious content."""
|
||||
|
||||
# Patterns that raise Warden alerts
|
||||
SUSPICIOUS_KEYWORDS = [
|
||||
"join our discord",
|
||||
"discord.gg",
|
||||
"grow your channel",
|
||||
"easy money",
|
||||
"limited offer",
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def is_suspicious(message: str) -> bool:
|
||||
"""
|
||||
Check if a message matches suspicious patterns.
|
||||
|
||||
Args:
|
||||
message: Message content
|
||||
|
||||
Returns:
|
||||
True if message is suspicious
|
||||
"""
|
||||
message_lower = message.lower()
|
||||
|
||||
for keyword in SuspiciousContentPolicy.SUSPICIOUS_KEYWORDS:
|
||||
if keyword in message_lower:
|
||||
logger.warning(f"Suspicious content detected: {keyword}")
|
||||
return True
|
||||
|
||||
# Check for multiple links
|
||||
link_count = message.count("http") + message.count("www")
|
||||
if link_count > 1:
|
||||
logger.warning("Suspicious content detected: multiple links")
|
||||
return True
|
||||
|
||||
return False
|
||||
Reference in New Issue
Block a user