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.

Overview

The Discord Exporter Bot generates digests in Obsidian-compatible markdown format with YAML frontmatter, bidirectional daily note links, and structured metadata. This allows seamless integration with Obsidian vaults for team knowledge management.

Obsidian Document Format

The format_obsidian_document function creates a fully-structured markdown document:
def format_obsidian_document(date_str: str, digest_content: str, stats: dict, config: Config) -> str:
    """Create Obsidian-formatted document."""
    prev_day = (datetime.strptime(date_str, "%Y-%m-%d") - timedelta(days=1)).strftime("%Y-%m-%d")
    next_day = (datetime.strptime(date_str, "%Y-%m-%d") + timedelta(days=1)).strftime("%Y-%m-%d")

    return f"""---
date: {date_str}
type: daily-digest
tags: [team, standup, daily]
contributors: {stats['contributor_count']}
messages: {stats['total_messages']}
channels: {stats['active_channels']}
---

# Team Digest - {date_str}

**📊 Activity Summary**
- {stats['total_messages']} messages across {stats['active_channels']} channels
- {stats['contributor_count']} active team members
- Time range: Last 24 hours

---

{digest_content}

---

**🔗 Links**
- [[{prev_day} - Team Digest|← Previous Day]]
- [[{next_day} - Team Digest|Next Day →]]

---
*Auto-generated at {datetime.now(config.eastern_tz).strftime('%I:%M %p ET')} from Discord*
"""

YAML Frontmatter Structure

The frontmatter contains structured metadata that Obsidian can query:
---
date: 2026-03-04
type: daily-digest
tags: [team, standup, daily]
contributors: 8
messages: 247
channels: 6
---

Metadata Fields

FieldTypePurposeExample
dateString (YYYY-MM-DD)ISO date of digest2026-03-04
typeStringDocument categorydaily-digest
tagsListSearchable tags[team, standup, daily]
contributorsIntegerUnique human contributors8
messagesIntegerTotal messages analyzed247
channelsIntegerActive channels6

Using Frontmatter in Obsidian

Dataview queries:
TABLE contributors, messages, channels
FROM "Daily Digests"
WHERE type = "daily-digest"
SORT date DESC
LIMIT 7
Filter by activity:
LIST
FROM "Daily Digests"
WHERE messages > 100
SORT date DESC
Chart activity trends:
TABLE date as Date, messages as Messages, contributors as Contributors
FROM "Daily Digests"
WHERE date >= date(today) - dur(30 days)
SORT date ASC
The bot automatically creates bidirectional links to adjacent days:
**🔗 Links**
- [[2026-03-03 - Team Digest|← Previous Day]]
- [[2026-03-05 - Team Digest|Next Day →]]
Link generation code:
prev_day = (datetime.strptime(date_str, "%Y-%m-%d") - timedelta(days=1)).strftime("%Y-%m-%d")
next_day = (datetime.strptime(date_str, "%Y-%m-%d") + timedelta(days=1)).strftime("%Y-%m-%d")
  • Sequential browsing: Click through days like a calendar
  • Temporal context: See what happened before/after
  • Graph view: Visualize daily digest timeline
  • Automatic backlinks: Obsidian creates reverse links automatically

How It Works in Obsidian

  1. Click ”← Previous Day” → Opens 2026-03-03 - Team Digest.md
  2. Graph view shows chain of all digests
  3. Backlinks panel shows days that reference current note
  4. Hover preview displays snippet without opening

Complete Document Example

Here’s a full Obsidian document as generated by the bot:
---
date: 2026-03-04
type: daily-digest
tags: [team, standup, daily]
contributors: 8
messages: 247
channels: 6
---

# Team Digest - 2026-03-04

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

---

## Individual Updates

**John Doe**
- Completed the authentication refactor and pushed to PR #234
- Fixed the database migration foreign key constraint issue
- Starting work on API rate limiting implementation

**Jane Smith**
- Reviewed authentication PR, left comments on error handling
- Updated API documentation with new endpoints
- Increased test coverage from 72% to 85%

**Mike Johnson**
- Worked through database migration issues (resolved with Sarah's help)
- Fixed mobile responsive issues on dashboard
- Deployed updates to staging environment

## Upcoming Work

- John: API rate limiting and throttling implementation
- Sarah: Search optimization and indexing improvements
- Mike: Dashboard redesign based on new mockups

## Blockers & Challenges

- Migration foreign key constraint (resolved)
- Waiting on design team for dashboard mockups
- Need to decide on logging framework (discussion ongoing)

## Key Decisions & Ideas

- **Decided**: Moving from MySQL to PostgreSQL for better JSON support
- **Idea**: Implement feature flags for gradual feature rollout
- **Agreement**: Shift to weekly deployment cadence

## Action Items

- [ ] John to document new API endpoints
- [ ] Sarah to schedule architecture review
- [ ] Team to vote on logging framework by Friday
- [ ] Mike to follow up with design team on mockups

---

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

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

File Naming Convention

The bot uses a consistent naming pattern for easy sorting:
filename = f"{output_path}/{date_str} - Team Digest.md"
# Example: Daily Digests/2026-03-04 - Team Digest.md
Naming structure:
  • Date prefix: YYYY-MM-DD (ISO 8601 format)
  • Separator: - (space-dash-space)
  • Description: Team Digest
  • Extension: .md
Why this format:
  1. Chronological sorting: Files sort by date automatically
  2. Human-readable: Easy to identify specific days
  3. Obsidian-friendly: Works with daily notes plugins
  4. Link-compatible: Can be referenced in wikilinks
  5. Cross-platform: No special characters that cause issues
Example file listing:
Daily Digests/
├── 2026-03-01 - Team Digest.md
├── 2026-03-02 - Team Digest.md
├── 2026-03-03 - Team Digest.md
├── 2026-03-04 - Team Digest.md
└── 2026-03-05 - Team Digest.md

Saving Digests

The save_digest function handles file creation and optional GitHub sync:
async def save_digest(digest_content: str, date_str: str, stats: dict, config: Config) -> str:
    """Save digest to local directory and push to GitHub."""
    output_path = get_output_path(config)

    # Initialize git repo if configured
    if config.github_repo_url:
        init_git_repo(output_path, config)

    os.makedirs(output_path, exist_ok=True)

    filename = f"{output_path}/{date_str} - Team Digest.md"
    obsidian_content = format_obsidian_document(date_str, digest_content, stats, config)

    with open(filename, "w", encoding="utf-8") as f:
        f.write(obsidian_content)

    logger.info(f"Saved digest to {filename}")

    # Commit and push to GitHub
    if config.github_repo_url:
        git_commit_and_push(filename, date_str, config)

    return filename
Output path logic:
def get_output_path(config: Config) -> str:
    """Determine the output path for digests."""
    if config.dokploy_volume_path and os.path.exists(config.dokploy_volume_path):
        logger.info(f"Saving to dokploy volume: {config.dokploy_volume_path}/{config.digests_dir}")
        return f"{config.dokploy_volume_path}/{config.digests_dir}"
    logger.info(f"Dokploy volume not found, saving to local: {config.digests_dir}")
    return config.digests_dir
  • Production: Saves to Docker volume (e.g., /mnt/digests/Daily Digests)
  • Local development: Saves to ./Daily Digests/

Using in Obsidian Vault

Setup Steps

  1. Configure output directory in .env:
    # Point to your Obsidian vault
    DOKPLOY_VOLUME_PATH="/path/to/your/ObsidianVault"
    
  2. Or use GitHub sync (recommended for teams):
    GITHUB_REPO_URL="https://github.com/yourorg/team-digests"
    GITHUB_TOKEN="ghp_your_token_here"
    
  3. Open in Obsidian:
    • File → Open vault → Select the directory containing Daily Digests/
    • Or add as existing vault if already open
Dataview - Query and visualize digest metadata:
TABLE messages, contributors, channels
FROM "Daily Digests"
WHERE date >= date(today) - dur(7 days)
SORT date DESC
Calendar - Visual calendar view of digests:
  • Shows digests on calendar grid
  • Click date to open that day’s digest
  • See activity density at a glance
Templater - Create custom digest views:
# Weekly Summary
<% tp.date.now("YYYY-[W]WW") %>

<%*
const files = app.vault.getMarkdownFiles()
  .filter(f => f.path.includes("Daily Digests"))
  .slice(0, 7);

for (let file of files) {
  tR += `- [[${file.basename}]]\n`;
}
%>
Graph View (built-in) - Visualize digest timeline:
  • Shows daily note links as connected chain
  • Identify gaps in digests
  • See backlinks and connections

Workflow Examples

Daily standup meeting:
  1. Open today’s digest in Obsidian
  2. Review “Individual Updates” section
  3. Discuss “Blockers & Challenges”
  4. Assign “Action Items” to team members
Weekly review:
  1. Use Dataview to list last 7 days
  2. Compare contributor counts and message volumes
  3. Identify trends (increasing blockers, declining activity)
Quarterly planning:
  1. Search for “Key Decisions” across all digests
  2. Review “Action Items” completion rate
  3. Analyze team velocity and capacity

GitHub Sync

The bot can automatically commit and push digests to GitHub:
def git_commit_and_push(file_path: str, date_str: str, config: Config) -> bool:
    """Commit and push the digest file to GitHub."""
    if not config.github_repo_url or not config.github_token:
        logger.warning("GitHub not configured, skipping push")
        return False

    output_path = os.path.dirname(file_path)

    try:
        # Configure git user (required for commits)
        subprocess.run(
            ["git", "-C", output_path, "config", "user.name", "Discord Bot"],
            check=True,
            capture_output=True
        )
        subprocess.run(
            ["git", "-C", output_path, "config", "user.email", "bot@discord.local"],
            check=True,
            capture_output=True
        )

        # Add the file
        subprocess.run(
            ["git", "-C", output_path, "add", os.path.basename(file_path)],
            check=True,
            capture_output=True
        )

        # Commit
        commit_message = f"Daily digest for {date_str}"
        subprocess.run(
            ["git", "-C", output_path, "commit", "-m", commit_message],
            check=True,
            capture_output=True,
            text=True
        )

        # Push with authentication
        auth_url = config.github_repo_url.replace(
            "https://",
            f"https://{config.github_token}@"
        )

        subprocess.run(
            ["git", "-C", output_path, "push", auth_url, "main"],
            check=True,
            capture_output=True,
            text=True
        )

        logger.info(f"Successfully pushed {file_path} to GitHub")
        return True

    except subprocess.CalledProcessError as e:
        logger.error(f"Git operation failed: {e.stderr if hasattr(e, 'stderr') else str(e)}")
        return False
Benefits of GitHub sync:
  • Team access: Everyone can access digests via GitHub
  • Version history: Track changes and modifications
  • Backup: Digests are safely stored remotely
  • Obsidian Sync alternative: Free sync solution using Git
Setup:
# .env configuration
GITHUB_REPO_URL="https://github.com/yourorg/team-digests"
GITHUB_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxxx"
Commit messages:
Daily digest for 2026-03-04

Metadata Generation

The activity summary is generated from export statistics:
# From format_obsidian_document function
**📊 Activity Summary**
- {stats['total_messages']} messages across {stats['active_channels']} channels
- {stats['contributor_count']} active team members
- Time range: Last 24 hours
Stats come from calculate_export_stats (see Message Export):
stats = {
    "total_messages": 247,
    "active_channels": 6,
    "contributors": {"John Doe", "Jane Smith", "Mike Johnson", ...},
    "contributor_count": 8
}
Each digest includes a generation timestamp:
*Auto-generated at {datetime.now(config.eastern_tz).strftime('%I:%M %p ET')} from Discord*
# Example: Auto-generated at 12:00 AM ET from Discord
Format: 12-hour time with AM/PM and Eastern Time zone See Timezone Handling for details on time conversion.