Skip to content

Commit 2be3736

Browse files
authored
Merge pull request #124 from ogajduse/pre-release-0.2.0-preparations
2 parents a632aed + 241be9c commit 2be3736

33 files changed

+5900
-4539
lines changed

.github/workflows/pull_request.yml

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ jobs:
1212
matrix:
1313
python-version: ["3.11"]
1414
steps:
15+
- name: Install xmllint
16+
run: sudo apt -y install libxml2-utils
17+
1518
- name: Checkout Repository
1619
uses: actions/checkout@v4
1720

.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,11 @@
33
build/
44
*.egg-info/
55
custom_components/hacs
6+
test_hass/.HA_VERSION
7+
test_hass/.storage*
8+
test_hass/blueprints/
9+
test_hass/custom_components
10+
test_hass/home-assistant_v2.db*
11+
test_hass/home-assistant.log*
12+
test_hass/sensors.yaml
13+
test_hass/www/

.pre-commit-config.yaml

+17-7
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,22 @@ repos:
88
- id: end-of-file-fixer
99
- id: check-yaml
1010
exclude: ^test_hass/configuration.yaml$
11+
- id: check-toml
1112
- id: debug-statements
12-
- repo: https://github.com/asottile/pyupgrade
13-
rev: v3.3.0
13+
- repo: https://github.com/lsst-ts/pre-commit-xmllint
14+
rev: v1.0.0
1415
hooks:
15-
- id: pyupgrade
16-
args: [--py311-plus]
17-
- repo: https://github.com/psf/black
18-
rev: "22.10.0"
16+
- id: format-xmllint
17+
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
18+
rev: v2.12.0
1919
hooks:
20-
- id: black
20+
- id: pretty-format-toml
21+
args: ["--autofix", "--no-sort"]
22+
- repo: https://github.com/abravalheri/validate-pyproject
23+
rev: v0.16
24+
hooks:
25+
- id: validate-pyproject
26+
additional_dependencies: ["validate-pyproject-schema-store[all]"]
2127
- repo: https://github.com/pre-commit/mirrors-mypy
2228
rev: "v1.4.1"
2329
hooks:
@@ -30,6 +36,10 @@ repos:
3036
types-PyYAML,
3137
types-requests,
3238
]
39+
- repo: https://github.com/psf/black
40+
rev: "22.10.0"
41+
hooks:
42+
- id: black
3343
- repo: https://github.com/astral-sh/ruff-pre-commit
3444
# Ruff version.
3545
rev: v0.0.278

.vscode/launch.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
3+
"version": "0.2.0",
4+
"configurations": [
5+
{
6+
// Example of attaching to local debug server
7+
"name": "Python: Attach Local",
8+
"type": "python",
9+
"request": "attach",
10+
"port": 5678,
11+
"host": "localhost",
12+
"pathMappings": [
13+
{
14+
"localRoot": "${workspaceFolder}",
15+
"remoteRoot": "."
16+
}
17+
],
18+
}
19+
]
20+
}

.vscode/settings.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
{
2-
"python.formatting.provider": "black"
2+
"python.formatting.provider": "black",
3+
"python.testing.pytestArgs": [
4+
"tests"
5+
],
6+
"python.testing.unittestEnabled": false,
7+
"python.testing.pytestEnabled": true
38
}

README.md

+42-17
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,54 @@ Hey dude! Help me out for a couple of :beers: or a :coffee:!
1515

1616
[![coffee](https://www.buymeacoffee.com/assets/img/custom_images/black_img.png)](https://www.buymeacoffee.com/zJtVxUAgH)
1717

18-
To get started put `/custom_components/feedparser/` here:
19-
`<config directory>/custom_components/feedparser/`
18+
19+
## Installation
20+
[![hacs_badge](https://img.shields.io/badge/HACS-Custom-41BDF5.svg)](https://github.com/hacs/integration)
21+
22+
1. Open HACS Settings and add this repository (https://github.com/custom-components/feedparser/)
23+
as a Custom Repository (use **Integration** as the category).
24+
2. The `feedparser` page should automatically load (or find it in the HACS Store)
25+
3. Click `Install`
26+
27+
Alternatively, click on the button below to add the repository:
28+
29+
[![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?category=Integration&repository=feedparser&owner=custom-components)
30+
31+
32+
## Configuration
2033

2134
**Example configuration.yaml:**
2235

2336
```yaml
2437
sensor:
25-
platform: feedparser
26-
name: Engineering Feed
27-
feed_url: 'https://www.sciencedaily.com/rss/matter_energy/engineering.xml'
28-
date_format: '%a, %d %b %Y %H:%M:%S %Z'
29-
scan_interval:
30-
hours: 3
31-
inclusions:
32-
- title
33-
- link
34-
- description
35-
- image
36-
- pubDate
37-
exclusions:
38-
- language
38+
- platform: feedparser
39+
name: Engineering Feed
40+
feed_url: 'https://www.sciencedaily.com/rss/matter_energy/engineering.xml'
41+
date_format: '%a, %d %b %Y %H:%M:%S %Z'
42+
scan_interval:
43+
hours: 3
44+
inclusions:
45+
- title
46+
- link
47+
- description
48+
- image
49+
- published
50+
exclusions:
51+
- language
52+
53+
# Configuration of the second sensor tracking a different RSS feed
54+
- platform: feedparser
55+
name: Algemeen
56+
feed_url: https://www.nu.nl/rss/Algemeen
57+
local_time: true
58+
show_topn: 1
3959
```
4060
61+
If you wish the integration to look for enclosures in the feed entries, add `image` to `inclusions` list. Do not use `enclosure`.
62+
The integration tries to get the link to an image for the given feed item and stores it under the attribute named `image`. If it fails to find it, it assigns the Home Assistant logo to it instead.
63+
64+
Note that the original `pubDate` field is available under `published` attribute for the given feed entry. Other date-type values that can be available are `updated`, `created` and `expired`. Please refer to [the documentation of the original feedparser](https://feedparser.readthedocs.io/en/latest/date-parsing.html) library.
65+
4166
**Configuration variables:**
4267

4368
key | description
@@ -65,6 +90,6 @@ Due to how `custom_components` are loaded, it is normal to see a `ModuleNotFound
6590
[forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg?style=for-the-badge
6691
[forum]: https://community.home-assistant.io/t/custom-component-rss-feed-parser/64637
6792
[license-shield]: https://img.shields.io/github/license/custom-components/feedparser.svg?style=for-the-badge
68-
[maintenance-shield]: https://img.shields.io/badge/maintainer-Ian%20Richardson%20%40iantrich-blue.svg?style=for-the-badge
93+
[maintenance-shield]: https://img.shields.io/badge/maintainer-Ondrej%20Gajdusek%20%40ogajduse-blue.svg?style=for-the-badge
6994
[releases-shield]: https://img.shields.io/github/release/custom-components/feedparser.svg?style=for-the-badge
7095
[releases]: https://github.com/custom-components/feedparser/releases

custom_components/feedparser/manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
"documentation": "https://github.com/custom-components/feedparser/blob/master/README.md",
77
"iot_class": "cloud_polling",
88
"issue_tracker": "https://github.com/custom-components/feedparser/issues",
9-
"requirements": ["feedparser==6.0.10", "python-dateutil"],
9+
"requirements": ["feedparser==6.0.11", "python-dateutil", "requests-file", "requests"],
1010
"version": "0.1.11"
1111
}

custom_components/feedparser/sensor.py

+26-12
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@
3535
CONF_INCLUSIONS = "inclusions"
3636
CONF_EXCLUSIONS = "exclusions"
3737
CONF_SHOW_TOPN = "show_topn"
38+
CONF_REMOVE_SUMMARY_IMG = "remove_summary_image"
3839

3940
DEFAULT_DATE_FORMAT = "%a, %b %d %I:%M %p"
4041
DEFAULT_SCAN_INTERVAL = timedelta(hours=1)
4142
DEFAULT_THUMBNAIL = "https://www.home-assistant.io/images/favicon-192x192-full.png"
4243
DEFAULT_TOPN = 9999
4344
USER_AGENT = f"Home Assistant Feed-parser Integration {__version__}"
45+
IMAGE_REGEX = r"<img.+?src=\"(.+?)\".+?>"
4446

4547
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
4648
{
@@ -49,6 +51,7 @@
4951
vol.Required(CONF_DATE_FORMAT, default=DEFAULT_DATE_FORMAT): cv.string,
5052
vol.Optional(CONF_LOCAL_TIME, default=False): cv.boolean,
5153
vol.Optional(CONF_SHOW_TOPN, default=DEFAULT_TOPN): cv.positive_int,
54+
vol.Optional(CONF_REMOVE_SUMMARY_IMG, default=False): cv.boolean,
5255
vol.Optional(CONF_INCLUSIONS, default=[]): vol.All(cv.ensure_list, [cv.string]),
5356
vol.Optional(CONF_EXCLUSIONS, default=[]): vol.All(cv.ensure_list, [cv.string]),
5457
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): cv.time_period,
@@ -72,6 +75,7 @@ async def async_setup_platform(
7275
name=config[CONF_NAME],
7376
date_format=config[CONF_DATE_FORMAT],
7477
show_topn=config[CONF_SHOW_TOPN],
78+
remove_summary_image=config[CONF_REMOVE_SUMMARY_IMG],
7579
inclusions=config[CONF_INCLUSIONS],
7680
exclusions=config[CONF_EXCLUSIONS],
7781
scan_interval=config[CONF_SCAN_INTERVAL],
@@ -95,6 +99,7 @@ def __init__(
9599
name: str,
96100
date_format: str,
97101
show_topn: int,
102+
remove_summary_image: bool,
98103
exclusions: list[str | None],
99104
inclusions: list[str | None],
100105
scan_interval: timedelta,
@@ -106,6 +111,7 @@ def __init__(
106111
self._attr_icon = "mdi:rss"
107112
self._date_format = date_format
108113
self._show_topn: int = show_topn
114+
self._remove_summary_image = remove_summary_image
109115
self._inclusions = inclusions
110116
self._exclusions = exclusions
111117
self._scan_interval = scan_interval
@@ -119,7 +125,9 @@ def __repr__(self: FeedParserSensor) -> str:
119125
"""Return the representation."""
120126
return (
121127
f'FeedParserSensor(name="{self.name}", feed="{self._feed}", '
122-
f"show_topn={self._show_topn}, inclusions={self._inclusions}, "
128+
f"show_topn={self._show_topn}, "
129+
f"remove_summary_image={self._remove_summary_image}, "
130+
f"inclusions={self._inclusions}, "
123131
f"exclusions={self._exclusions}, scan_interval={self._scan_interval}, "
124132
f'local_time={self._local_time}, date_format="{self._date_format}")'
125133
)
@@ -191,14 +199,20 @@ def _generate_sensor_entry(
191199
else:
192200
sensor_entry[key] = value
193201

194-
if "image" in self._inclusions and "image" not in sensor_entry:
195-
sensor_entry["image"] = self._process_image(feed_entry)
196-
if (
197-
"link" in self._inclusions
198-
and "link" not in sensor_entry
199-
and (processed_link := self._process_link(feed_entry))
200-
):
201-
sensor_entry["link"] = processed_link
202+
if "image" in self._inclusions and "image" not in sensor_entry:
203+
sensor_entry["image"] = self._process_image(feed_entry)
204+
if (
205+
"link" in self._inclusions
206+
and "link" not in sensor_entry
207+
and (processed_link := self._process_link(feed_entry))
208+
):
209+
sensor_entry["link"] = processed_link
210+
if self._remove_summary_image and "summary" in sensor_entry:
211+
sensor_entry["summary"] = re.sub(
212+
IMAGE_REGEX,
213+
"",
214+
sensor_entry["summary"],
215+
)
202216
_LOGGER.debug("Feed %s: Generated sensor entry: %s", self.name, sensor_entry)
203217
return sensor_entry
204218

@@ -239,7 +253,7 @@ def _parse_date(self: FeedParserSensor, date: str) -> datetime:
239253
return parsed_time
240254

241255
def _process_image(self: FeedParserSensor, feed_entry: FeedParserDict) -> str:
242-
if "enclosures" in feed_entry and feed_entry["enclosures"]:
256+
if feed_entry.get("enclosures"):
243257
images = [
244258
enc for enc in feed_entry["enclosures"] if enc.type.startswith("image/")
245259
]
@@ -248,7 +262,7 @@ def _process_image(self: FeedParserSensor, feed_entry: FeedParserDict) -> str:
248262
return images[0]["href"]
249263
elif "summary" in feed_entry:
250264
images = re.findall(
251-
r"<img.+?src=\"(.+?)\".+?>",
265+
IMAGE_REGEX,
252266
feed_entry["summary"],
253267
)
254268
if images:
@@ -265,7 +279,7 @@ def _process_link(self: FeedParserSensor, feed_entry: FeedParserDict) -> str:
265279
"""Return link from feed entry."""
266280
if "links" in feed_entry:
267281
if len(feed_entry["links"]) > 1:
268-
_LOGGER.warning(
282+
_LOGGER.debug(
269283
"Feed %s: More than one link found for %s. Using the first link.",
270284
self.name,
271285
feed_entry,

0 commit comments

Comments
 (0)