255 lines
7.0 KiB
Bash
Executable File
255 lines
7.0 KiB
Bash
Executable File
#!/bin/bash
|
|
# FreshRSS Smart Digest
|
|
# Pulls unread articles, ranks them by relevance, delivers categorized summary
|
|
# Runs daily at 7:00 AM (before AI newsletter digest)
|
|
|
|
set -e
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "$SCRIPT_DIR/../../.env" 2>/dev/null || true
|
|
|
|
# Config
|
|
FRESHRSS_URL="${FRESHRSS_URL:-http://freshrss.kangaroo-eel.ts.net}"
|
|
FRESHRSS_USER="${FRESHRSS_USER:-anthony}"
|
|
TELEGRAM_CHAT="${TELEGRAM_CHAT:-1793951355}"
|
|
NTFY_URL="${NTFY_URL:-}"
|
|
NTFY_TOPIC="${NTFY_TOPIC:-}"
|
|
NTFY_MIN_PRIORITY="${NTFY_MIN_PRIORITY:-4}"
|
|
|
|
# Your interest keywords for relevance ranking
|
|
INTERESTS=(
|
|
"AI" "artificial intelligence" "machine learning" "LLM"
|
|
"renewable energy" "solar" "wind" "battery" "EV" "electric vehicle"
|
|
"politics" "Labor" "election" "government"
|
|
"LGBTQ" "LGBT" "queer" "transgender"
|
|
"Perth" "Western Australia" "WA"
|
|
"climate" "environment"
|
|
"mental health" "depression" "therapy"
|
|
)
|
|
|
|
# Priority sources (always include these)
|
|
PRIORITY_SOURCES=(
|
|
"CNN"
|
|
"MSNBC"
|
|
"Al Jazeera"
|
|
"ABC News"
|
|
"The Guardian"
|
|
"BlueSky"
|
|
)
|
|
|
|
log() {
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
|
}
|
|
|
|
fetch_unread() {
|
|
log "Fetching unread articles from FreshRSS..."
|
|
|
|
local raw_output
|
|
raw_output=$(/home/openclaw/.openclaw/workspace/skills/freshrss-reader/scripts/freshrss.sh headlines \
|
|
--unread --count 50 2>/dev/null) || {
|
|
log "ERROR: Failed to fetch from FreshRSS"
|
|
return 1
|
|
}
|
|
|
|
echo "$raw_output"
|
|
}
|
|
|
|
# Score an article based on interest keywords
|
|
score_article() {
|
|
local title="$1"
|
|
local source="$2"
|
|
local categories="$3"
|
|
local score=0
|
|
local text="${title,,} ${categories,,}"
|
|
|
|
# Check interest keywords
|
|
for interest in "${INTERESTS[@]}"; do
|
|
if [[ "$text" == *"${interest,,}"* ]]; then
|
|
((score+=2))
|
|
fi
|
|
done
|
|
|
|
# Priority sources get a boost
|
|
for priority in "${PRIORITY_SOURCES[@]}"; do
|
|
if [[ "$source" == *"$priority"* ]]; then
|
|
((score+=3))
|
|
fi
|
|
done
|
|
|
|
echo "$score"
|
|
}
|
|
|
|
# Parse articles and build digest
|
|
build_digest() {
|
|
local raw="$1"
|
|
local output=""
|
|
local must_read=""
|
|
local skimmable=""
|
|
local total_count=0
|
|
|
|
# Parse the output - each article is 3 lines: date/source/title, URL, categories
|
|
local current_date=""
|
|
local current_source=""
|
|
local current_title=""
|
|
local current_url=""
|
|
local current_cats=""
|
|
|
|
while IFS= read -r line; do
|
|
# Skip empty lines
|
|
[[ -z "$line" ]] && continue
|
|
|
|
# Check if this is a title line (starts with [)
|
|
if [[ "$line" =~ ^\[.*\].*:.*$ ]]; then
|
|
# Save previous article if exists
|
|
if [[ -n "$current_title" ]]; then
|
|
local score
|
|
score=$(score_article "$current_title" "$current_source" "$current_cats")
|
|
|
|
local article_formatted="• *${current_source}*: ${current_title}\n ${current_url}\n"
|
|
|
|
if ((score >= 5)); then
|
|
must_read+="$article_formatted\n"
|
|
elif ((score >= 2)); then
|
|
skimmable+="$article_formatted\n"
|
|
fi
|
|
|
|
((total_count++))
|
|
fi
|
|
|
|
# Parse new article
|
|
current_date=$(echo "$line" | sed 's/^\[\([^]]*\)\].*/\1/')
|
|
current_source=$(echo "$line" | sed 's/^\[[^]]*\] \([^:]*\):.*/\1/')
|
|
current_title=$(echo "$line" | sed 's/^\[[^]]*\] [^:]*: //')
|
|
current_url=""
|
|
current_cats=""
|
|
|
|
# Check if this is a URL line
|
|
elif [[ "$line" =~ ^https?:// ]]; then
|
|
current_url="$line"
|
|
|
|
# Check if this is categories line
|
|
elif [[ "$line" =~ ^Categories:\ ]]; then
|
|
current_cats=$(echo "$line" | sed 's/Categories: //')
|
|
fi
|
|
done <<< "$raw"
|
|
|
|
# Don't forget the last article
|
|
if [[ -n "$current_title" ]]; then
|
|
local score
|
|
score=$(score_article "$current_title" "$current_source" "$current_cats")
|
|
|
|
local article_formatted="• *${current_source}*: ${current_title}\n ${current_url}\n"
|
|
|
|
if ((score >= 5)); then
|
|
must_read+="$article_formatted\n"
|
|
elif ((score >= 2)); then
|
|
skimmable+="$article_formatted\n"
|
|
fi
|
|
|
|
((total_count++))
|
|
fi
|
|
|
|
# Build final message
|
|
output="📰 *FreshRSS Daily Digest*\n\n"
|
|
output+="Found *$total_count* unread articles.\n\n"
|
|
|
|
if [[ -n "$must_read" ]]; then
|
|
output+="🔥 *Must Read*\n$must_read\n"
|
|
fi
|
|
|
|
if [[ -n "$skimmable" ]]; then
|
|
output+="📎 *Skimmable*\n$skimmable\n"
|
|
fi
|
|
|
|
if [[ -z "$must_read" && -z "$skimmable" ]]; then
|
|
output+="No high-priority articles today. You're caught up! 🎉\n"
|
|
fi
|
|
|
|
output+="\n📖 [Open FreshRSS]($FRESHRSS_URL)"
|
|
|
|
echo -e "$output"
|
|
}
|
|
|
|
send_telegram() {
|
|
local message="$1"
|
|
|
|
log "Sending to Telegram..."
|
|
|
|
if [[ -z "${TELEGRAM_BOT_TOKEN:-}" ]]; then
|
|
log "ERROR: TELEGRAM_BOT_TOKEN is not set"
|
|
return 1
|
|
fi
|
|
|
|
local tg_response
|
|
tg_response=$(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
|
|
}") || {
|
|
log "ERROR: Failed to send Telegram message"
|
|
return 1
|
|
}
|
|
|
|
if ! echo "$tg_response" | jq -e '.ok == true' > /dev/null 2>&1; then
|
|
log "ERROR: Telegram API rejected message: $tg_response"
|
|
return 1
|
|
fi
|
|
|
|
log "Sent to Telegram successfully"
|
|
}
|
|
|
|
send_ntfy() {
|
|
local title="$1"
|
|
local message="$2"
|
|
local priority="${3:-4}"
|
|
local sound="${4:-default}"
|
|
|
|
[[ -z "$NTFY_URL" ]] && return 0
|
|
[[ -z "$NTFY_TOPIC" ]] && return 0
|
|
|
|
# Enforce minimum priority (default 4)
|
|
if [[ "$priority" =~ ^[0-9]+$ ]] && [[ "$NTFY_MIN_PRIORITY" =~ ^[0-9]+$ ]]; then
|
|
if (( priority < NTFY_MIN_PRIORITY )); then
|
|
priority="$NTFY_MIN_PRIORITY"
|
|
fi
|
|
fi
|
|
|
|
log "Sending to ntfy..."
|
|
curl -s -X POST "${NTFY_URL%/}/${NTFY_TOPIC}" \
|
|
-H "Title: $title" \
|
|
-H "Priority: $priority" \
|
|
-H "Sound: $sound" \
|
|
-d "$message" > /dev/null || {
|
|
log "ERROR: Failed to send ntfy message"
|
|
return 1
|
|
}
|
|
log "Sent to ntfy successfully"
|
|
}
|
|
|
|
main() {
|
|
log "Starting FreshRSS digest..."
|
|
|
|
# Fetch articles
|
|
local raw_articles
|
|
raw_articles=$(fetch_unread) || exit 1
|
|
|
|
# Build digest
|
|
local digest
|
|
digest=$(build_digest "$raw_articles")
|
|
|
|
# Send to both channels
|
|
send_telegram "$digest"
|
|
|
|
# Plain text version for ntfy (strip markdown)
|
|
local plain_digest
|
|
plain_digest=$(echo -e "$digest" | sed 's/\*//g' | sed 's/\[\([^]]*\)\]([^)]*)/\1/g')
|
|
send_ntfy "FreshRSS Digest" "$plain_digest" 4 default
|
|
|
|
log "Digest complete!"
|
|
}
|
|
|
|
main "$@"
|