grok has changed the code for me, i donβt have any code knowledge.
The code works on some feeds not all, there is no general fix, i had to copy the code from the sensor.py and the rss feed data to grok because not every rss feed uses the same format what can be read by RSS Accordion. So if people like to use it and like to change it with grok, make a copy of your sensor.py from here /homeassistant/custom_components/feedparser and the feed data from developers β> states β> sensor.yourfeed entity (just from one feed entry).
Its the same as the media player from the card, on some podcast RSS feeds it shows but not all just because the formats used in the feed is different and the player is not shown. So there is no need to to add the code to github repo.
Below is the config for the card feed i use for feedparser
- platform: feedparser
name: Techniek en Wetenschap
feed_url: 'https://tw.nl/feed'
date_format: "%a %d %b %Y %H:%M"
scan_interval:
hours: 2
show_topn: 5
inclusions:
- title
- summary
- link
- published
- description
- id
- duration
- image
- media
## Techniek en Wetenschap
- platform: template
sensors:
techniek_en_wetenschap_attributes_0:
friendly_name: "Techniek en Wetenschap Content 0"
value_template: >
{{ states.sensor.techniek_en_wetenschap.attributes.entries[0].title }}
and below is the sensor.py
from __future__ import annotations
import re
import locale
from datetime import timedelta
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from dateutil import parser as date_parser
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
from homeassistant.const import CONF_NAME, CONF_SCAN_INTERVAL
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
import homeassistant.util.dt as dt_util
import feedparser
__version__ = "0.1.16"
CONF_FEED_URL = "feed_url"
CONF_DATE_FORMAT = "date_format"
CONF_LOCAL_TIME = "local_time"
CONF_INCLUSIONS = "inclusions"
CONF_EXCLUSIONS = "exclusions"
CONF_SHOW_TOPN = "show_topn"
DEFAULT_SCAN_INTERVAL = timedelta(minutes=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_FEED_URL): cv.string,
vol.Required(CONF_DATE_FORMAT, default="%a, %d %b %Y %H:%M"): cv.string,
vol.Optional(CONF_LOCAL_TIME, default=True): cv.boolean,
vol.Optional(CONF_SHOW_TOPN, default=9999): cv.positive_int,
vol.Optional(CONF_INCLUSIONS, default=[]): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_EXCLUSIONS, default=[]): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): cv.time_period,
})
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_devices: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the feedparser sensor."""
async_add_devices([FeedParserSensor(
feed=config[CONF_FEED_URL],
name=config[CONF_NAME],
date_format=config[CONF_DATE_FORMAT],
local_time=config[CONF_LOCAL_TIME],
show_topn=config[CONF_SHOW_TOPN],
inclusions=config[CONF_INCLUSIONS],
exclusions=config[CONF_EXCLUSIONS],
scan_interval=config.get(CONF_SCAN_INTERVAL),
)], True)
class FeedParserSensor(SensorEntity):
def __init__(self, feed, name, date_format, local_time, show_topn, inclusions, exclusions, scan_interval):
self._feed = feed
self._attr_name = name
self._attr_icon = "mdi:rss-box"
self._date_format = date_format
self._local_time = local_time
self._show_topn = show_topn
self._inclusions = [inc.lower() for inc in (inclusions or [])]
self._exclusions = [exc.lower() for exc in (exclusions or [])]
self._scan_interval = scan_interval
self._state = 0
self._entries = []
@property
def state(self):
return self._state
@property
def extra_state_attributes(self):
return {"entries": self._entries}
def _parse_date(self, date_str: str):
"""Parse date with Dutch locale support (ma, di, wo, etc.)"""
if not date_str:
return None
# Temporarily set locale to Dutch for correct abbr weekday/month parsing
original_locale = locale.getlocale(locale.LC_TIME)
try:
locale.setlocale(locale.LC_TIME, ('nl_NL', 'UTF-8'))
except locale.Error:
try:
locale.setlocale(locale.LC_TIME, 'Dutch_Netherlands')
except locale.Error:
pass # fallback to default
try:
dt = date_parser.parse(date_str, fuzzy=False, ignoretz=True)
if self._local_time:
dt = dt_util.as_local(dt)
return dt.strftime(self._date_format)
except Exception:
return date_str # return raw if all fails
finally:
# Restore original locale
if original_locale:
locale.setlocale(locale.LC_TIME, original_locale)
def _extract_image(self, entry):
"""Extract highest quality image β optimized for Telegraaf"""
image_url = None
# 1. media:content with medium="image" and highest width
if getattr(entry, "media_content", None):
images = [
m["url"] for m in entry.media_content
if m.get("medium") == "image" and m.get("url")
]
if images:
# Telegraaf uses width="1200" for main image
wide_images = [url for url in images if "1200" in url or "1000" in url]
image_url = wide_images[0] if wide_images else images[0]
# 2. media:thumbnail (fallback)
if not image_url and getattr(entry, "media_thumbnail", None):
thumb = entry.media_thumbnail
if isinstance(thumb, list):
thumb = thumb[0]
image_url = thumb.get("url")
# 3. enclosures (image/*)
if not image_url and getattr(entry, "enclosures", None):
for enc in entry.enclosures:
if enc.type and enc.type.startswith("image/"):
image_url = enc.get("href")
break
# 4. Open Graph image in summary_detail (Telegraaf often puts og:image here)
if not image_url and hasattr(entry, "summary_detail"):
match = re.search(r'src=["\']([^"\']+telegraaf\.nl[^"\']+1200[^"\']*)["\']', entry.summary_detail.value or "", re.I)
if match:
image_url = match.group(1)
# 5. Fallback: any img tag in summary
if not image_url and hasattr(entry, "summary"):
match = re.search(r'src=["\']([^"\']+)["\']', entry.summary)
if match:
image_url = match.group(1)
return image_url or "https://www.home-assistant.io/images/favicon-192x192-full.png"
def update(self):
parsed_feed = feedparser.parse(self._feed)
if not parsed_feed or not parsed_feed.get("entries"):
self._state = 0
self._entries = []
return
entries = parsed_feed.entries[:self._show_topn]
self._state = len(entries)
self._entries = []
for entry in entries:
item = {}
for key, value in entry.items():
lower_key = key.lower()
# Skip excluded or not included
if self._exclusions and lower_key in self._exclusions:
continue
if self._inclusions and lower_key not in self._inclusions and lower_key != "image":
continue
if lower_key.startswith("parsed"):
continue
# Special handling for known date fields
if lower_key in ["published", "updated", "created", "expired", "date"]:
if value:
item[key] = self._parse_date(value)
continue
item[key] = value
# Always try to add best possible image
if not self._inclusions or "image" in self._inclusions:
item["image"] = self._extract_image(entry)
# Optional: clean up title/link/summary if you want
# item["title"] = re.sub(r"\s+", " ", entry.title).strip()
self._entries.append(item)