Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/trustlessmatt/discord-exporter-bot/llms.txt

Use this file to discover all available pages before exploring further.

Functions for generating AI-powered daily digests from Discord message exports, formatting them as Obsidian-compatible markdown, and pushing to GitHub.

run_digest_pipeline()

async def run_digest_pipeline(
    bot: commands.Bot,
    config: Config,
    hours: int
) -> Optional[dict]:
    """Run the complete digest pipeline: export, analyze, save."""
Executes the complete digest generation pipeline: exports messages, generates AI digest with Claude, formats as Obsidian markdown, saves locally, and pushes to GitHub.

Parameters

bot
commands.Bot
required
Discord bot instance with active connection.
config
Config
required
Configuration with API keys, paths, and digest settings.
hours
int
required
Time range in hours for message export.

Returns

return
Optional[dict]
Result dictionary with success status, digest content, and metadata.
Success Response:
{
    "success": True,
    "digest": "# Individual Updates\n\n**Alice**..."  # Full markdown digest
    "digest_path": "Daily Digests/2026-03-04 - Team Digest.md",
    "stats": {
        "total_messages": 1547,
        "active_channels": 8,
        "contributors": {"Alice", "Bob", "Charlie"},
        "contributor_count": 15
    },
    "export_filename": "exports/discord_export_20260304_000000_ET.json"
}
Error Response:
{
    "success": False,
    "error": "Export failed"  # or "Failed to generate digest"
}

Pipeline Steps

  1. Export: Calls perform_export() to gather messages from all channels
  2. Generate: Calls generate_daily_digest() to create AI summary with Claude
  3. Save: Calls save_digest() to format as Obsidian markdown and save to disk
  4. Push: Automatically commits and pushes to GitHub if configured
This is the main entry point for digest generation. Use this function instead of calling individual steps.

generate_daily_digest()

async def generate_daily_digest(
    export_data: dict,
    config: Config
) -> Optional[str]:
    """Use Claude to generate structured daily digest."""
Uses Claude API to analyze Discord transcript and generate a structured daily digest with individual updates, blockers, decisions, and action items.

Parameters

export_data
dict
required
Export data dictionary from perform_export() containing channels and messages.
config
Config
required
Configuration with anthropic_api_key, digest_model, and digest_max_tokens.

Returns

return
Optional[str]
Markdown-formatted digest text generated by Claude, or None if API call fails.
Example Output:
# Individual Updates

**Alice**
- Completed the authentication refactor, merged PR #234
- Started working on the new dashboard component
- Fixed bug with OAuth token refresh

**Bob**
- Reviewed 5 pull requests
- Updated documentation for the API endpoints
- Deployed v2.1.0 to staging

# Upcoming Work

- Alice: Planning to implement the user profile page this week
- Bob: Will focus on performance optimization next

# Blockers & Challenges

- Charlie is waiting for design mockups before proceeding with the landing page
- Database migration is taking longer than expected

# Key Decisions & Ideas

- Team decided to switch from REST to GraphQL for the new API
- Discussed implementing feature flags for gradual rollout

# Action Items

- [ ] Alice to review Bob's infrastructure PR
- [ ] Schedule meeting about Q2 planning
- [ ] Update staging environment credentials

Digest Structure

Claude is prompted to extract:
  1. Individual Updates - Work completed or in progress by each team member
  2. Upcoming Work - Planned tasks and goals mentioned
  3. Blockers & Challenges - Obstacles and requests for help
  4. Key Decisions & Ideas - Important discussions and decisions
  5. Action Items - Specific TODOs and follow-ups

Behavior

  • Filters out bot messages using filter_bot_messages()
  • Calls prepare_transcript() to format messages for Claude
  • Uses configured model (default: claude-haiku-4-5-20251001)
  • Returns None if anthropic_api_key is not configured
  • Returns None if API call fails (logs error)
Requires ANTHROPIC_API_KEY environment variable to be set. Returns None if missing.

prepare_transcript()

def prepare_transcript(export_data: dict) -> str:
    """Prepare transcript for Claude analysis from export data."""
Formats export data into a readable transcript for Claude analysis, organized by channel with timestamps and cleaned message content.

Parameters

export_data
dict
required
Export data dictionary containing channels and messages from perform_export().

Returns

return
str
Formatted transcript text with channel sections, timestamps, and messages. Returns “No messages to analyze.” if empty.
Transcript Format:
## Channel: #general
[2026-03-04T09:15:30] Alice: Just deployed the new feature to production
[2026-03-04T09:16:45] Bob: Looks great! Testing the API endpoints now
[2026-03-04T09:18:12] Alice: Let me know if you hit any issues

## Channel: #engineering
[2026-03-04T10:30:00] Charlie: Working on the database migration
[2026-03-04T11:45:20] Alice: Need any help with that?

Behavior

  • Groups messages by channel with ## Channel: #name headers
  • Filters out bot messages (only includes human messages)
  • Uses content_clean field for readable mentions
  • Truncates timestamps to YYYY-MM-DDTHH:MM:SS format
  • Skips channels with no non-bot messages
  • Returns fallback text if no messages exist

save_digest()

async def save_digest(
    digest_content: str,
    date_str: str,
    stats: dict,
    config: Config
) -> str:
    """Save digest to local directory and push to GitHub."""
Saves digest content as Obsidian-formatted markdown file with frontmatter, navigation links, and optional GitHub sync.

Parameters

digest_content
str
required
Markdown digest content generated by Claude.
date_str
str
required
Date string in YYYY-MM-DD format for filename and frontmatter.
stats
dict
required
Statistics dictionary from calculate_export_stats() with message counts and contributors.
config
Config
required
Configuration with directory paths and optional GitHub settings.

Returns

return
str
Full file path where digest was saved.

Behavior

  1. Determines output path using get_output_path() (Dokploy volume or local)
  2. Initializes/clones Git repo if github_repo_url is configured
  3. Creates directory if it doesn’t exist
  4. Formats content using format_obsidian_document()
  5. Saves to {date} - Team Digest.md
  6. Commits and pushes to GitHub if configured
File naming format: 2026-03-04 - Team Digest.md

format_obsidian_document()

def format_obsidian_document(
    date_str: str,
    digest_content: str,
    stats: dict,
    config: Config
) -> str:
    """Create Obsidian-formatted document."""
Formats digest content into Obsidian-compatible markdown with YAML frontmatter, navigation links, and metadata.

Parameters

date_str
str
required
Date in YYYY-MM-DD format.
digest_content
str
required
Main digest content from Claude.
stats
dict
required
Statistics with total_messages, active_channels, and contributor_count.
config
Config
required
Configuration for timezone display.

Returns

return
str
Complete markdown document with frontmatter, content, and navigation.
Output Structure:
---
date: 2026-03-04
type: daily-digest
tags: [team, standup, daily]
contributors: 15
messages: 1547
channels: 8
---

# Team Digest - 2026-03-04

**📊 Activity Summary**
- 1547 messages across 8 channels
- 15 active team members
- Time range: Last 24 hours

---

{digest_content}

---

**🔗 Links**
- [[2026-03-03 - Team Digest|← Previous Day]]
- [[2026-03-05 - Team Digest|Next Day →]]

---
*Auto-generated at 12:00 AM ET from Discord*

Features

  • YAML Frontmatter: Obsidian-compatible metadata for filtering and search
  • Activity Summary: Quick statistics at the top
  • Navigation Links: Wikilink-style links to previous/next day digests
  • Timestamp: Generation time in Eastern Time
  • Tags: Pre-configured tags for categorization in Obsidian

Git Integration Functions

init_git_repo()

def init_git_repo(output_path: str, config: Config) -> bool:
    """Initialize or clone git repository."""
Initializes Git repository in the output directory or clones from GitHub if not present. Parameters:
  • output_path (str): Directory path for digests
  • config (Config): Configuration with github_repo_url and github_token
Returns: bool - True if successful, False otherwise Behavior:
  • If .git exists: runs git fetch and git merge origin/main
  • If no .git: clones repository using authenticated URL
  • Falls back to git init + remote add if clone fails
  • Requires both github_repo_url and github_token in config

git_commit_and_push()

def git_commit_and_push(
    file_path: str,
    date_str: str,
    config: Config
) -> bool:
    """Commit and push the digest file to GitHub."""
Commits the digest file and pushes to GitHub with an automated commit message. Parameters:
  • file_path (str): Full path to digest file
  • date_str (str): Date string for commit message
  • config (Config): Configuration with GitHub credentials
Returns: bool - True if successful, False otherwise Behavior:
  • Configures git user as “Discord Bot <bot@discord.local>
  • Stages the specific digest file
  • Creates commit with message: "Daily digest for {date_str}"
  • Pushes to main branch using authenticated URL
  • Returns False if GitHub not configured
Git operations are synchronous and may block. Consider running in executor for async contexts.

Complete Pipeline Example

from discord.ext import commands, tasks
from bot import Config, run_digest_pipeline
from datetime import datetime
import logging

logger = logging.getLogger(__name__)
config = Config.from_env()
bot = commands.Bot(command_prefix="!", intents=intents)

# Manual trigger via command
@bot.command(name="digest")
async def manual_digest(ctx, hours: int = 24):
    """Generate digest on demand"""
    await ctx.send(f"Generating digest for last {hours} hours...")
    
    result = await run_digest_pipeline(bot, config, hours)
    
    if result["success"]:
        preview = result["digest"][:500] + "..."
        await ctx.send(
            f"✅ Digest generated!\n\n{preview}\n\n"
            f"Full digest: `{result['digest_path']}`"
        )
    else:
        await ctx.send(f"❌ Error: {result['error']}")

# Automatic scheduled digest
@tasks.loop(hours=24)
async def daily_digest_task():
    """Runs automatically at scheduled time"""
    logger.info("Starting scheduled digest generation")
    
    result = await run_digest_pipeline(bot, config, 24)
    
    if result["success"]:
        logger.info(f"Digest completed: {result['digest_path']}")
    else:
        logger.error(f"Digest failed: {result['error']}")

@daily_digest_task.before_loop
async def wait_for_scheduled_time():
    """Wait until midnight ET"""
    await bot.wait_until_ready()
    
    now = datetime.now(config.eastern_tz)
    target = now.replace(hour=0, minute=0, second=0, microsecond=0)
    
    if now >= target:
        target += timedelta(days=1)
    
    wait_seconds = (target - now).total_seconds()
    logger.info(f"Waiting {wait_seconds/3600:.1f} hours until first digest")
    await asyncio.sleep(wait_seconds)

# Start the scheduled task
@bot.event
async def on_ready():
    if not daily_digest_task.is_running():
        daily_digest_task.start()