#!/usr/bin/env bash
# 🌐 Trilium Sitemap 自动更新 — 快速单页提取模式
set -o pipefail
BASE="https://trilium.atibm.com"
ALIAS="hermes"
OUTFILE="/tmp/sitemap-latest.xml"
NOW=$(date -u +%Y-%m-%d)
echo "🌐 获取 Trilium 共享主页..." >&2
HTML=$(curl -s --max-time 8 -o - "${BASE}/share/${ALIAS}" 2>/dev/null) || {
echo "❌ 无法获取共享主页" >&2
exit 1
}
echo "🔗 提取链接..." >&2
ALL_LINKS=$(echo "$HTML" | grep -oP 'href="\./[^"]+"' | sed 's/href="\.\///;s/"//g' | sort -u)
VALID_LINKS=""
while IFS= read -r link; do
[[ "$link" =~ \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot)$ ]] && continue
if [[ "$link" =~ ^[A-Za-z0-9]{8,15}$ ]]; then
VALID_LINKS+="${BASE}/share/${link}"$'\n'
elif [[ "$link" =~ ^[a-zA-Z][a-zA-Z0-9]{4,30}$ ]]; then
VALID_LINKS+="${BASE}/share/${link}"$'\n'
elif [[ "$link" =~ ^CVE- ]]; then
VALID_LINKS+="${BASE}/share/${link}"$'\n'
fi
done <<< "$ALL_LINKS"
COUNT=$(echo "$VALID_LINKS" | grep -c .)
echo " 提取到 ${COUNT} 个有效链接" >&2
# ── 密码保护过滤 ──
ETAPI_TOKEN="${TRILIUM_ETAPI_TOKEN}"
if [ -z "$ETAPI_TOKEN" ] && [ -f /opt/data/.env ]; then
ETAPI_TOKEN=$(grep '^TRILIUM_ETAPI_TOKEN=' /opt/data/.env | cut -d= -f2- | tr -d '"'"'"')
fi
if [ -z "$ETAPI_TOKEN" ] && [ -f "$HOME/.env" ]; then
ETAPI_TOKEN=$(grep '^TRILIUM_ETAPI_TOKEN=' "$HOME/.env" | cut -d= -f2- | tr -d '"'"'"')
fi
if [ -n "$ETAPI_TOKEN" ] && [ "$COUNT" -gt 0 ]; then
echo "🔒 查询密码保护笔记..." >&2
PROTECTED_CODES=$(curl -s --max-time 10 \
-H "Authorization: ***" \
"${BASE}/etapi/notes?search=%23shareCredentials" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
exclude=set()
for r in d.get('results',[]):
nid=r.get('noteId','')
if not nid: continue
has_cred=any(a.get('name')=='shareCredentials' for a in r.get('attributes',[]))
if not has_cred: continue
exclude.add(nid)
for a in r.get('attributes',[]):
if a.get('name')=='shareAlias' and a.get('value'):
exclude.add(a['value'])
for e in sorted(exclude): print(e)
")
PROTECTED_COUNT=$(echo "$PROTECTED_CODES" | grep -c . 2>/dev/null || echo 0)
if [ "$PROTECTED_COUNT" -gt 0 ]; then
echo " 找到 ${PROTECTED_COUNT} 个密码保护标识符,过滤中..." >&2
FILTERED=""; REMOVED=0
while IFS= read -r url; do
[ -z "$url" ] && continue
nid=$(echo "$url" | sed 's|.*/share/||')
if echo "$PROTECTED_CODES" | grep -qxF "$nid"; then
REMOVED=$((REMOVED + 1))
else
FILTERED+="$url"$'\n'
fi
done <<< "$VALID_LINKS"
VALID_LINKS="$FILTERED"
COUNT=$((COUNT - REMOVED))
echo " 🔒 已移除 ${REMOVED} 个密码保护链接" >&2
fi
fi
# 生成 sitemap XML
{
echo '<?xml version="1.0" encoding="utf-8"?>'
echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
echo ' <url>'
echo " <loc>${BASE}/share/${ALIAS}</loc>"
echo " <lastmod>${NOW}</lastmod>"
echo ' <changefreq>daily</changefreq>'
echo ' <priority>1.0</priority>'
echo ' </url>'
while IFS= read -r url; do
[ -z "$url" ] && continue
echo ' <url>'
echo " <loc>${url}</loc>"
echo " <lastmod>${NOW}</lastmod>"
echo ' <changefreq>weekly</changefreq>'
echo ' <priority>0.6</priority>'
echo ' </url>'
done <<< "$VALID_LINKS"
echo '</urlset>'
} > "$OUTFILE"
SIZE=$(wc -c < "$OUTFILE")
echo " sitemap.xml: ${SIZE} bytes, ${COUNT} URLs" >&2
ETAPI_NOTE_ID="${TRILIUM_SITEMAP_NOTE_ID:-387btDIOMxHp}"
if [ -n "$ETAPI_TOKEN" ]; then
ETAPI_URL="https://trilium.atibm.com/etapi/notes/${ETAPI_NOTE_ID}/content"
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X PUT "$ETAPI_URL" \
-H "Authorization: ***" \
-H "Content-Type: text/plain" \
--data-binary @"$OUTFILE" 2>&1)
echo " ETAPI update: HTTP ${HTTP_CODE}" >&2
else
echo " ⚠️ TRILIUM_ETAPI_TOKEN 未设置,跳过 ETAPI 更新" >&2
fi
echo "✅ 完成: ${BASE}/sitemap.xml (${SIZE} bytes, ${COUNT} URLs)" >&2
cat "$OUTFILE"