feat(bot/extension/watcher): Discord 아티팩트 알림 개선 — 파일 첨부 전송, truncation 확대, 동적 .md 감시
This commit is contained in:
130
bot.py
130
bot.py
@@ -491,34 +491,49 @@ class GravityBot(commands.Bot):
|
||||
event_label = "생성" if event.event_type == EventType.FILE_CREATED else "업데이트"
|
||||
|
||||
full_content = event.content.strip()
|
||||
CHUNK_SIZE = 4000 # Discord embed desc limit is 4096
|
||||
if not full_content:
|
||||
full_content = "(빈 파일)"
|
||||
|
||||
# Split into chunks for long content
|
||||
chunks = []
|
||||
while full_content:
|
||||
chunks.append(full_content[:CHUNK_SIZE])
|
||||
full_content = full_content[CHUNK_SIZE:]
|
||||
FILE_ATTACH_THRESHOLD = 4000 # Above this, send as file attachment
|
||||
|
||||
if not chunks:
|
||||
chunks = ["(빈 파일)"]
|
||||
if len(full_content) > FILE_ATTACH_THRESHOLD:
|
||||
# Long content → summary embed + file attachment
|
||||
# Extract first meaningful paragraph for summary
|
||||
summary_lines = []
|
||||
for line in full_content.split('\n'):
|
||||
if line.strip():
|
||||
summary_lines.append(line.strip())
|
||||
if len('\n'.join(summary_lines)) > 300:
|
||||
break
|
||||
summary = '\n'.join(summary_lines[:5])
|
||||
if len(summary) > 500:
|
||||
summary = summary[:500] + '...'
|
||||
|
||||
# First chunk with title
|
||||
embed = discord.Embed(
|
||||
title=f"{label} ({event_label}됨)",
|
||||
description=chunks[0],
|
||||
color=discord.Color.blue(),
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
)
|
||||
embed.set_footer(text=f"Session: {event.conversation_id[:8]}")
|
||||
await channel.send(embed=embed)
|
||||
|
||||
# Additional chunks if content is long
|
||||
for i, chunk in enumerate(chunks[1:], 2):
|
||||
embed = discord.Embed(
|
||||
title=f"{label} (계속 {i}/{len(chunks)})",
|
||||
description=chunk,
|
||||
title=f"{label} ({event_label}됨)",
|
||||
description=f"{summary}\n\n📎 *전체 내용은 첨부 파일을 확인하세요* ({len(full_content):,}자)",
|
||||
color=discord.Color.blue(),
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
)
|
||||
embed.set_footer(text=f"Session: {event.conversation_id[:8]}")
|
||||
|
||||
# Create in-memory file attachment
|
||||
import io
|
||||
file_bytes = full_content.encode('utf-8')
|
||||
discord_file = discord.File(
|
||||
io.BytesIO(file_bytes),
|
||||
filename=event.file_name,
|
||||
)
|
||||
await channel.send(embed=embed, file=discord_file)
|
||||
else:
|
||||
# Short content → inline embed (original behavior)
|
||||
embed = discord.Embed(
|
||||
title=f"{label} ({event_label}됨)",
|
||||
description=full_content,
|
||||
color=discord.Color.blue(),
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
)
|
||||
embed.set_footer(text=f"Session: {event.conversation_id[:8]}")
|
||||
await channel.send(embed=embed)
|
||||
|
||||
# ─── Approval Scanner ────────────────────────────────────────────
|
||||
@@ -823,31 +838,80 @@ class GravityBot(commands.Bot):
|
||||
data = json.loads(f.read_text(encoding="utf-8-sig"))
|
||||
project = data.get("project_name", Config.PROJECT_NAME)
|
||||
content = data.get("content", "")
|
||||
attached_files = data.get("attached_files", [])
|
||||
|
||||
if content:
|
||||
if content or attached_files:
|
||||
channel = await self._get_channel(project)
|
||||
if channel:
|
||||
# Split long content
|
||||
CHUNK = 4000
|
||||
chunks = [content[i:i+CHUNK] for i in range(0, len(content), CHUNK)]
|
||||
for i, chunk in enumerate(chunks):
|
||||
title = "💬 AI 대화 내용" if i == 0 else f"💬 (계속 {i+1}/{len(chunks)})"
|
||||
import io
|
||||
|
||||
# ── Send attached files (from Extension's writeChatSnapshotWithFiles) ──
|
||||
discord_files = []
|
||||
for af in attached_files:
|
||||
af_name = af.get("name", "document.md")
|
||||
af_content = af.get("content", "")
|
||||
if af_content:
|
||||
discord_files.append(discord.File(
|
||||
io.BytesIO(af_content.encode("utf-8")),
|
||||
filename=af_name,
|
||||
))
|
||||
|
||||
FILE_ATTACH_THRESHOLD = 4000
|
||||
if len(content) > FILE_ATTACH_THRESHOLD:
|
||||
# Long chat content → summary embed + file attachment
|
||||
summary = content[:500].rsplit('\n', 1)[0]
|
||||
embed = discord.Embed(
|
||||
title=title,
|
||||
description=chunk,
|
||||
title="💬 AI 대화 내용",
|
||||
description=f"{summary}\n\n📎 *전체 내용은 첨부 파일 참조* ({len(content):,}자)",
|
||||
color=discord.Color.purple(),
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
)
|
||||
# Add content itself as file attachment
|
||||
discord_files.append(discord.File(
|
||||
io.BytesIO(content.encode("utf-8")),
|
||||
filename="chat_message.md",
|
||||
))
|
||||
try:
|
||||
await channel.send(embed=embed, files=discord_files)
|
||||
except discord.NotFound:
|
||||
logger.warning(f"Channel deleted for {project}, re-creating...")
|
||||
self.project_channels.pop(project, None)
|
||||
channel = await self._get_channel(project)
|
||||
if channel:
|
||||
# Re-create files (discord.File consumed after send)
|
||||
discord_files2 = []
|
||||
for af in attached_files:
|
||||
af_name = af.get("name", "document.md")
|
||||
af_content = af.get("content", "")
|
||||
if af_content:
|
||||
discord_files2.append(discord.File(
|
||||
io.BytesIO(af_content.encode("utf-8")),
|
||||
filename=af_name,
|
||||
))
|
||||
discord_files2.append(discord.File(
|
||||
io.BytesIO(content.encode("utf-8")),
|
||||
filename="chat_message.md",
|
||||
))
|
||||
await channel.send(embed=embed, files=discord_files2)
|
||||
else:
|
||||
# Short content → inline embed (original)
|
||||
embed = discord.Embed(
|
||||
title="💬 AI 대화 내용",
|
||||
description=content,
|
||||
color=discord.Color.purple(),
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
)
|
||||
try:
|
||||
await channel.send(embed=embed)
|
||||
await channel.send(
|
||||
embed=embed,
|
||||
files=discord_files if discord_files else discord.utils.MISSING,
|
||||
)
|
||||
except discord.NotFound:
|
||||
# Channel was deleted — invalidate cache and retry once
|
||||
logger.warning(f"Channel deleted for {project}, re-creating...")
|
||||
self.project_channels.pop(project, None)
|
||||
channel = await self._get_channel(project)
|
||||
if channel:
|
||||
await channel.send(embed=embed)
|
||||
break
|
||||
|
||||
f.unlink() # Cleanup
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
|
||||
Reference in New Issue
Block a user