diff --git a/automations/newsletter-digest/README.md b/automations/newsletter-digest/README.md new file mode 100644 index 0000000..5d7ad36 --- /dev/null +++ b/automations/newsletter-digest/README.md @@ -0,0 +1,50 @@ +# Smart Newsletter Digest + +**Runs:** Daily at 8:00 PM (Australia/Perth) +**Sends to:** Telegram + Gotify +**Learns from your feedback!** + +## How It Works + +1. **Fetches** newsletters from past 24 hours via IMAP +2. **Scores** them based on your preferences +3. **Summarizes** key points and highlights +4. **Sends** digest to Telegram +5. **Learns** from your feedback + +## Feedback System + +After each digest, reply with: + +| Reply | Effect | +|-------|--------| +| 👍 | Reinforces current selections | +| 👎 | Adjusts to show less like this | +| `more AI` | Adds "AI" to liked topics | +| `less crypto` | Adds "crypto" to disliked topics | +| `prefer The Rundown` | Boosts this source | + +## Preferences File + +`newsletter-preferences.json` stores: +- `liked_topics` - Topics to prioritize +- `disliked_topics` - Topics to de-emphasize +- `preferred_sources` - Senders to boost +- `avoided_sources` - Senders to skip +- `feedback_count` - How many times you've given feedback + +## Manual Run + +```bash +./smart-digest.sh +``` + +## Files + +- `smart-digest.sh` - Main script +- `newsletter-preferences.json` - Your learned preferences +- `digest-history.json` - Last 7 days of digests (for feedback reference) + +--- + +**Krilly will learn what you like!** 🦀 diff --git a/automations/newsletter-digest/newsletter-preferences.json b/automations/newsletter-digest/newsletter-preferences.json new file mode 100644 index 0000000..d1e1473 --- /dev/null +++ b/automations/newsletter-digest/newsletter-preferences.json @@ -0,0 +1,9 @@ +{ + "liked_topics": ["AI", "machine learning", "productivity", "automation"], + "disliked_topics": [], + "preferred_sources": ["The Rundown", "AI Valley", "TLDR"], + "avoided_sources": [], + "preferred_length": "medium", + "feedback_count": 0, + "last_feedback": null +} diff --git a/automations/newsletter-digest/smart-digest.sh b/automations/newsletter-digest/smart-digest.sh new file mode 100755 index 0000000..e8c5d14 --- /dev/null +++ b/automations/newsletter-digest/smart-digest.sh @@ -0,0 +1,265 @@ +#!/bin/bash +# Smart Newsletter Digest with Feedback Learning +# Runs daily at 8 PM, summarizes newsletters, asks for feedback, learns preferences + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/../../.env" 2>/dev/null || true + +TELEGRAM_CHAT="${TELEGRAM_CHAT:-1793951355}" +GOTIFY_URL="${GOTIFY_URL:-http://runtipi.kangaroo-eel.ts.net:8129}" +GOTIFY_TOKEN="${GOTIFY_TOKEN:-AGKnHafW3FGzBlt}" +PREFERENCES_FILE="$SCRIPT_DIR/newsletter-preferences.json" +DIGEST_HISTORY="$SCRIPT_DIR/digest-history.json" + +# Newsletter sources to track +NEWSLETTER_DOMAINS=( + "aivalley.ai" + "thedeepview.com" + "ai-secret" + "therundown.ai" + "tldr.tech" + "benedict.substack.com" + "theinformation.com" + "platformer" + "notion.so" +) + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" +} + +# Initialize preferences if not exists +init_preferences() { + if [[ ! -f "$PREFERENCES_FILE" ]]; then + cat > "$PREFERENCES_FILE" << 'EOF' +{ + "liked_topics": [], + "disliked_topics": [], + "preferred_sources": [], + "avoided_sources": [], + "preferred_length": "medium", + "feedback_count": 0, + "last_feedback": null +} +EOF + log "Created preferences file" + fi +} + +# Fetch newsletters from past 24 hours using IMAP +fetch_newsletters() { + log "Fetching newsletters from past 24 hours..." + + local yesterday=$(date -d "24 hours ago" +%Y-%m-%d) + + # Use the imap-smtp-email skill to fetch emails + local emails + emails=$(/home/openclaw/.openclaw/workspace/skills/imap-smtp-email/imap-py.py \ + --search "SINCE $yesterday" \ + --folder "INBOX" 2>/dev/null) || { + log "WARNING: Failed to fetch emails" + echo "[]" + return + } + + # Filter for newsletter domains + echo "$emails" | python3 -c " +import sys +import json + +try: + emails = json.load(sys.stdin) +except: + emails = [] + +newsletters = [] +domains = $(printf '%s\n' "${NEWSLETTER_DOMAINS[@]}" | jq -R . | jq -s .) + +for email in emails: + sender = email.get('from', '').lower() + subject = email.get('subject', '') + + for domain in domains: + if domain.lower() in sender: + newsletters.append({ + 'from': email.get('from', ''), + 'subject': subject, + 'date': email.get('date', ''), + 'body': email.get('body', '')[:2000], + 'id': email.get('id', '') + }) + break + +print(json.dumps(newsletters)) +" +} + +# Build digest with preferences +build_digest() { + local newsletters="$1" + local preferences=$(cat "$PREFERENCES_FILE") + + python3 << PYEOF +import json +import sys + +newsletters = json.loads('''$newsletters''') +prefs = json.loads('''$preferences''') + +liked = prefs.get('liked_topics', []) +disliked = prefs.get('disliked_topics', []) +preferred_sources = prefs.get('preferred_sources', []) + +# Score each newsletter +scored = [] +for nl in newsletters: + score = 0 + sender = nl.get('from', '').lower() + subject = nl.get('subject', '').lower() + body = nl.get('body', '').lower() + + # Boost preferred sources + for src in preferred_sources: + if src.lower() in sender: + score += 3 + + # Check for liked topics + for topic in liked: + if topic.lower() in subject or topic.lower() in body: + score += 2 + + # Check for disliked topics + for topic in disliked: + if topic.lower() in subject or topic.lower() in body: + score -= 3 + + # Extract key bits + key_bits = [] + body_text = nl.get('body', '') + + # Find bullet points and important sentences + lines = body_text.split('\n') + for line in lines: + line = line.strip() + if line.startswith('•') or line.startswith('-') or line.startswith('*'): + if len(line) > 20 and len(line) < 200: + key_bits.append(line) + elif any(kw in line.lower() for kw in ['new:', 'update:', 'launch', 'announce', 'breakthrough', 'important', 'key', 'top']): + if len(line) > 30 and len(line) < 200: + key_bits.append(line) + + nl['score'] = score + nl['key_bits'] = key_bits[:5] + scored.append(nl) + +# Sort by score +scored.sort(key=lambda x: x['score'], reverse=True) + +# Build digest +output = [] +output.append("📬 *Newsletter Digest* — $(date '+%B %d, %Y')") +output.append("") +output.append(f"Found {len(newsletters)} newsletters in past 24 hours.") +output.append("") + +for i, nl in enumerate(scored[:10]): + emoji = "🔥" if nl['score'] > 2 else "📌" if nl['score'] > 0 else "📄" + output.append(f"{emoji} *{nl['subject'][:80]}*") + output.append(f" From: {nl['from'][:50]}") + + if nl['key_bits']: + output.append(" Key points:") + for bit in nl['key_bits'][:3]: + clean_bit = bit.replace('*', '').replace('_', '')[:100] + output.append(f" • {clean_bit}") + output.append("") + +output.append("━━━━━━━━━━━━━━━━━━━━") +output.append("") +output.append("🤔 *Feedback?* Reply with:") +output.append("👍 if this was useful") +output.append("👎 if not relevant") +output.append("💬 + topic you want more/less of") + +print('\n'.join(output)) +PYEOF +} + +send_telegram() { + local message="$1" + + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + -H "Content-Type: application/json" \ + -d "{ + \"chat_id\": \"$TELEGRAM_CHAT\", + \"text\": \"$message\", + \"parse_mode\": \"Markdown\", + \"disable_web_page_preview\": true + }" > /dev/null || { + log "ERROR: Failed to send Telegram message" + return 1 + } +} + +send_gotify() { + local title="$1" + local message="$2" + + curl -s -X POST "${GOTIFY_URL}/message?token=${GOTIFY_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{ + \"title\": \"$title\", + \"message\": \"$message\", + \"priority\": 5 + }" > /dev/null || true +} + +# Save digest for feedback reference +save_digest() { + local newsletters="$1" + + local today=$(date +%Y-%m-%d) + + if [[ ! -f "$DIGEST_HISTORY" ]]; then + echo '{"digests": []}' > "$DIGEST_HISTORY" + fi + + local temp_file=$(mktemp) + jq --arg date "$today" \ + --argjson news "$newsletters" \ + '.digests += [{"date": $date, "newsletters": $news, "feedback_received": false}] | .digests |= .[-7:]' \ + "$DIGEST_HISTORY" > "$temp_file" + mv "$temp_file" "$DIGEST_HISTORY" +} + +main() { + log "Starting newsletter digest..." + + init_preferences + + local newsletters + newsletters=$(fetch_newsletters) + + local count=$(echo "$newsletters" | jq 'length') + log "Found $count newsletters" + + if ((count == 0)); then + local no_news="📭 *Newsletter Digest*\n\nNo newsletters found in the past 24 hours.\n\nEither your inbox is clean or we need to adjust the newsletter sources!" + send_telegram "$no_news" + return 0 + fi + + local digest + digest=$(build_digest "$newsletters") + + send_telegram "$digest" + send_gotify "Newsletter Digest" "$count newsletters summarized - check Telegram" + + save_digest "$newsletters" + + log "Digest sent successfully!" +} + +main "$@"