Spaces:
Running
Running
Ig0tU Claude Sonnet 4.6 commited on
Commit ·
a3efffb
1
Parent(s): f6a2352
Fix RSS sync: switch to feedparser, add feed_title to output
Browse filesxml.etree.ElementTree was failing silently on CDATA links and encoding
declarations. feedparser handles all RSS/Atom variants correctly.
Also: show feed title in dashboard output, better empty-feed messaging.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- requirements.txt +1 -0
- signalmesh_api.py +28 -23
requirements.txt
CHANGED
|
@@ -2,3 +2,4 @@ fastapi==0.115.6
|
|
| 2 |
uvicorn[standard]==0.32.1
|
| 3 |
pydantic==2.10.3
|
| 4 |
httpx==0.28.1
|
|
|
|
|
|
| 2 |
uvicorn[standard]==0.32.1
|
| 3 |
pydantic==2.10.3
|
| 4 |
httpx==0.28.1
|
| 5 |
+
feedparser==6.0.11
|
signalmesh_api.py
CHANGED
|
@@ -45,8 +45,8 @@ _app_pkg.logger = _logger_pkg.logger
|
|
| 45 |
|
| 46 |
import asyncio
|
| 47 |
import re
|
| 48 |
-
import xml.etree.ElementTree as ET
|
| 49 |
|
|
|
|
| 50 |
import httpx
|
| 51 |
from fastapi import FastAPI, Header, HTTPException, Request
|
| 52 |
from fastapi.middleware.cors import CORSMiddleware
|
|
@@ -446,9 +446,13 @@ async function doRssSync() {
|
|
| 446 |
appendFeed('rss-feed', `<span class="tag err">ERR</span>${escHtml(String(d.error||d.detail))}`);
|
| 447 |
return;
|
| 448 |
}
|
| 449 |
-
|
| 450 |
-
(d.items
|
| 451 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 452 |
);
|
| 453 |
refreshMesh();
|
| 454 |
}
|
|
@@ -465,7 +469,7 @@ async function doContextDiscovery() {
|
|
| 465 |
}
|
| 466 |
appendFeed('ctx-feed', `<span class="tag live">FOUND</span><b>${d.urls_found}</b> URLs · <b>${d.signals_broadcast}</b> broadcast · <b>${(d.rss_feeds_synced||[]).length}</b> RSS feeds`);
|
| 467 |
(d.rss_feeds_synced||[]).forEach(r =>
|
| 468 |
-
appendFeed('ctx-feed', `<span class="tag rss">RSS</span>${escHtml(r.url?.slice(0,50))} → ${r.
|
| 469 |
);
|
| 470 |
refreshMesh();
|
| 471 |
}
|
|
@@ -695,32 +699,32 @@ async def tool_rss_sync(
|
|
| 695 |
):
|
| 696 |
"""
|
| 697 |
**Tool: rss_matrix_sync**
|
| 698 |
-
Fetch an RSS feed and sync each item into the spatial grid via SHA-256 URI routing.
|
| 699 |
-
Each feed item is broadcast onto its deterministically assigned grid node.
|
| 700 |
"""
|
| 701 |
_check_key(x_signalmesh_key)
|
|
|
|
|
|
|
| 702 |
try:
|
| 703 |
async with httpx.AsyncClient(timeout=30.0) as client:
|
| 704 |
-
resp = await client.get(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 705 |
resp.raise_for_status()
|
|
|
|
| 706 |
except Exception as e:
|
| 707 |
-
raise HTTPException(status_code=502, detail=f"
|
| 708 |
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
raise HTTPException(status_code=422, detail=f"Invalid XML in feed: {e}")
|
| 713 |
|
| 714 |
-
items = root_el.findall(".//item") or root_el.findall(".//{http://www.w3.org/2005/Atom}entry")
|
| 715 |
synced = []
|
| 716 |
-
for
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
title = title_el.text if title_el is not None else "Untitled"
|
| 722 |
-
link = (link_el.text or link_el.get("href", "")) if link_el is not None else ""
|
| 723 |
-
desc = (desc_el.text or "")[:400] if desc_el is not None else ""
|
| 724 |
|
| 725 |
if not link:
|
| 726 |
continue
|
|
@@ -732,7 +736,7 @@ async def tool_rss_sync(
|
|
| 732 |
signal_registry.broadcast(
|
| 733 |
name=f"rss_{agent_kw}",
|
| 734 |
source_type="rss_cp",
|
| 735 |
-
data={"title": title, "link": link, "summary":
|
| 736 |
metadata={"node": node_idx, "agent": agent_kw, "feed_url": req.url},
|
| 737 |
)
|
| 738 |
synced.append({"title": title, "node": node_idx, "agent": agent_kw, "link": link})
|
|
@@ -740,6 +744,7 @@ async def tool_rss_sync(
|
|
| 740 |
return {
|
| 741 |
"synced": len(synced),
|
| 742 |
"feed_url": req.url,
|
|
|
|
| 743 |
"items": synced[:20],
|
| 744 |
"signals_in_mesh": _signals_in_mesh(),
|
| 745 |
}
|
|
|
|
| 45 |
|
| 46 |
import asyncio
|
| 47 |
import re
|
|
|
|
| 48 |
|
| 49 |
+
import feedparser
|
| 50 |
import httpx
|
| 51 |
from fastapi import FastAPI, Header, HTTPException, Request
|
| 52 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
| 446 |
appendFeed('rss-feed', `<span class="tag err">ERR</span>${escHtml(String(d.error||d.detail))}`);
|
| 447 |
return;
|
| 448 |
}
|
| 449 |
+
const feedLabel = d.feed_title ? `<b style="color:var(--accent2)">${escHtml(d.feed_title)}</b> · ` : '';
|
| 450 |
+
appendFeed('rss-feed', `<span class="tag rss">RSS</span>${feedLabel}<b>${d.synced}</b> items → ${d.signals_in_mesh} signals in mesh`);
|
| 451 |
+
if (d.synced === 0) {
|
| 452 |
+
appendFeed('rss-feed', `<span class="dim">Feed returned no items. Try a different URL.</span>`);
|
| 453 |
+
}
|
| 454 |
+
(d.items||[]).slice(0,10).forEach(it =>
|
| 455 |
+
appendFeed('rss-feed', `<span class="dim">↳ node <b>${it.node}</b> (${it.agent})</span> ${escHtml(it.title?.slice(0,70)||'')}`)
|
| 456 |
);
|
| 457 |
refreshMesh();
|
| 458 |
}
|
|
|
|
| 469 |
}
|
| 470 |
appendFeed('ctx-feed', `<span class="tag live">FOUND</span><b>${d.urls_found}</b> URLs · <b>${d.signals_broadcast}</b> broadcast · <b>${(d.rss_feeds_synced||[]).length}</b> RSS feeds`);
|
| 471 |
(d.rss_feeds_synced||[]).forEach(r =>
|
| 472 |
+
appendFeed('ctx-feed', `<span class="tag rss">RSS</span>${escHtml(r.url?.slice(0,50))} → ${r.error ? '<span class="tag err">ERR</span> '+escHtml(r.error) : '<b>'+r.synced+'</b> items'}`)
|
| 473 |
);
|
| 474 |
refreshMesh();
|
| 475 |
}
|
|
|
|
| 699 |
):
|
| 700 |
"""
|
| 701 |
**Tool: rss_matrix_sync**
|
| 702 |
+
Fetch an RSS/Atom feed and sync each item into the spatial grid via SHA-256 URI routing.
|
|
|
|
| 703 |
"""
|
| 704 |
_check_key(x_signalmesh_key)
|
| 705 |
+
|
| 706 |
+
# feedparser handles RSS 0.9x/1.0/2.0, Atom, encoding, CDATA — all of it
|
| 707 |
try:
|
| 708 |
async with httpx.AsyncClient(timeout=30.0) as client:
|
| 709 |
+
resp = await client.get(
|
| 710 |
+
req.url,
|
| 711 |
+
follow_redirects=True,
|
| 712 |
+
headers={"User-Agent": "ToolTuner-SignalMesh/1.0 (RSS reader; +https://huggingface.co/spaces/acecalisto3/ToolTuner)"},
|
| 713 |
+
)
|
| 714 |
resp.raise_for_status()
|
| 715 |
+
raw = resp.text
|
| 716 |
except Exception as e:
|
| 717 |
+
raise HTTPException(status_code=502, detail=f"Fetch failed: {e}")
|
| 718 |
|
| 719 |
+
feed = feedparser.parse(raw)
|
| 720 |
+
if feed.bozo and not feed.entries:
|
| 721 |
+
raise HTTPException(status_code=422, detail=f"Feed parse error: {feed.bozo_exception}")
|
|
|
|
| 722 |
|
|
|
|
| 723 |
synced = []
|
| 724 |
+
for entry in feed.entries:
|
| 725 |
+
title = entry.get("title", "Untitled")
|
| 726 |
+
link = entry.get("link", "") or entry.get("id", "")
|
| 727 |
+
summary = entry.get("summary", entry.get("description", ""))[:400]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 728 |
|
| 729 |
if not link:
|
| 730 |
continue
|
|
|
|
| 736 |
signal_registry.broadcast(
|
| 737 |
name=f"rss_{agent_kw}",
|
| 738 |
source_type="rss_cp",
|
| 739 |
+
data={"title": title, "link": link, "summary": summary},
|
| 740 |
metadata={"node": node_idx, "agent": agent_kw, "feed_url": req.url},
|
| 741 |
)
|
| 742 |
synced.append({"title": title, "node": node_idx, "agent": agent_kw, "link": link})
|
|
|
|
| 744 |
return {
|
| 745 |
"synced": len(synced),
|
| 746 |
"feed_url": req.url,
|
| 747 |
+
"feed_title": feed.feed.get("title", ""),
|
| 748 |
"items": synced[:20],
|
| 749 |
"signals_in_mesh": _signals_in_mesh(),
|
| 750 |
}
|