Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release preparations for 0.2.0 #124

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ jobs:
matrix:
python-version: ["3.11"]
steps:
- name: Install xmllint
run: sudo apt -y install libxml2-utils

- name: Checkout Repository
uses: actions/checkout@v4

Expand Down
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,11 @@
build/
*.egg-info/
custom_components/hacs
test_hass/.HA_VERSION
test_hass/.storage*
test_hass/blueprints/
test_hass/custom_components
test_hass/home-assistant_v2.db*
test_hass/home-assistant.log*
test_hass/sensors.yaml
test_hass/www/
24 changes: 17 additions & 7 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@ repos:
- id: end-of-file-fixer
- id: check-yaml
exclude: ^test_hass/configuration.yaml$
- id: check-toml
- id: debug-statements
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.0
- repo: https://github.com/lsst-ts/pre-commit-xmllint
rev: v1.0.0
hooks:
- id: pyupgrade
args: [--py311-plus]
- repo: https://github.com/psf/black
rev: "22.10.0"
- id: format-xmllint
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.12.0
hooks:
- id: black
- id: pretty-format-toml
args: ["--autofix", "--no-sort"]
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.16
hooks:
- id: validate-pyproject
additional_dependencies: ["validate-pyproject-schema-store[all]"]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.4.1"
hooks:
Expand All @@ -30,6 +36,10 @@ repos:
types-PyYAML,
types-requests,
]
- repo: https://github.com/psf/black
rev: "22.10.0"
hooks:
- id: black
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.278
Expand Down
20 changes: 20 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
// Example of attaching to local debug server
"name": "Python: Attach Local",
"type": "python",
"request": "attach",
"port": 5678,
"host": "localhost",
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "."
}
],
}
]
}
7 changes: 6 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
{
"python.formatting.provider": "black"
"python.formatting.provider": "black",
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
59 changes: 42 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,54 @@ Hey dude! Help me out for a couple of :beers: or a :coffee:!

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

To get started put `/custom_components/feedparser/` here:
`<config directory>/custom_components/feedparser/`

## Installation
[![hacs_badge](https://img.shields.io/badge/HACS-Custom-41BDF5.svg)](https://github.com/hacs/integration)

1. Open HACS Settings and add this repository (https://github.com/custom-components/feedparser/)
as a Custom Repository (use **Integration** as the category).
2. The `feedparser` page should automatically load (or find it in the HACS Store)
3. Click `Install`

Alternatively, click on the button below to add the repository:

[![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)


## Configuration

**Example configuration.yaml:**

```yaml
sensor:
platform: feedparser
name: Engineering Feed
feed_url: 'https://www.sciencedaily.com/rss/matter_energy/engineering.xml'
date_format: '%a, %d %b %Y %H:%M:%S %Z'
scan_interval:
hours: 3
inclusions:
- title
- link
- description
- image
- pubDate
exclusions:
- language
- platform: feedparser
name: Engineering Feed
feed_url: 'https://www.sciencedaily.com/rss/matter_energy/engineering.xml'
date_format: '%a, %d %b %Y %H:%M:%S %Z'
scan_interval:
hours: 3
inclusions:
- title
- link
- description
- image
- published
exclusions:
- language

# Configuration of the second sensor tracking a different RSS feed
- platform: feedparser
name: Algemeen
feed_url: https://www.nu.nl/rss/Algemeen
local_time: true
show_topn: 1
```

If you wish the integration to look for enclosures in the feed entries, add `image` to `inclusions` list. Do not use `enclosure`.
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.

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.

**Configuration variables:**

key | description
Expand Down Expand Up @@ -65,6 +90,6 @@ Due to how `custom_components` are loaded, it is normal to see a `ModuleNotFound
[forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg?style=for-the-badge
[forum]: https://community.home-assistant.io/t/custom-component-rss-feed-parser/64637
[license-shield]: https://img.shields.io/github/license/custom-components/feedparser.svg?style=for-the-badge
[maintenance-shield]: https://img.shields.io/badge/maintainer-Ian%20Richardson%20%40iantrich-blue.svg?style=for-the-badge
[maintenance-shield]: https://img.shields.io/badge/maintainer-Ondrej%20Gajdusek%20%40ogajduse-blue.svg?style=for-the-badge
[releases-shield]: https://img.shields.io/github/release/custom-components/feedparser.svg?style=for-the-badge
[releases]: https://github.com/custom-components/feedparser/releases
2 changes: 1 addition & 1 deletion custom_components/feedparser/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"documentation": "https://github.com/custom-components/feedparser/blob/master/README.md",
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/custom-components/feedparser/issues",
"requirements": ["feedparser==6.0.10", "python-dateutil"],
"requirements": ["feedparser==6.0.11", "python-dateutil", "requests-file", "requests"],
"version": "0.1.11"
}
38 changes: 26 additions & 12 deletions custom_components/feedparser/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@
CONF_INCLUSIONS = "inclusions"
CONF_EXCLUSIONS = "exclusions"
CONF_SHOW_TOPN = "show_topn"
CONF_REMOVE_SUMMARY_IMG = "remove_summary_image"

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

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
Expand All @@ -49,6 +51,7 @@
vol.Required(CONF_DATE_FORMAT, default=DEFAULT_DATE_FORMAT): cv.string,
vol.Optional(CONF_LOCAL_TIME, default=False): cv.boolean,
vol.Optional(CONF_SHOW_TOPN, default=DEFAULT_TOPN): cv.positive_int,
vol.Optional(CONF_REMOVE_SUMMARY_IMG, default=False): cv.boolean,
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,
Expand All @@ -72,6 +75,7 @@ async def async_setup_platform(
name=config[CONF_NAME],
date_format=config[CONF_DATE_FORMAT],
show_topn=config[CONF_SHOW_TOPN],
remove_summary_image=config[CONF_REMOVE_SUMMARY_IMG],
inclusions=config[CONF_INCLUSIONS],
exclusions=config[CONF_EXCLUSIONS],
scan_interval=config[CONF_SCAN_INTERVAL],
Expand All @@ -95,6 +99,7 @@ def __init__(
name: str,
date_format: str,
show_topn: int,
remove_summary_image: bool,
exclusions: list[str | None],
inclusions: list[str | None],
scan_interval: timedelta,
Expand All @@ -106,6 +111,7 @@ def __init__(
self._attr_icon = "mdi:rss"
self._date_format = date_format
self._show_topn: int = show_topn
self._remove_summary_image = remove_summary_image
self._inclusions = inclusions
self._exclusions = exclusions
self._scan_interval = scan_interval
Expand All @@ -119,7 +125,9 @@ def __repr__(self: FeedParserSensor) -> str:
"""Return the representation."""
return (
f'FeedParserSensor(name="{self.name}", feed="{self._feed}", '
f"show_topn={self._show_topn}, inclusions={self._inclusions}, "
f"show_topn={self._show_topn}, "
f"remove_summary_image={self._remove_summary_image}, "
f"inclusions={self._inclusions}, "
f"exclusions={self._exclusions}, scan_interval={self._scan_interval}, "
f'local_time={self._local_time}, date_format="{self._date_format}")'
)
Expand Down Expand Up @@ -191,14 +199,20 @@ def _generate_sensor_entry(
else:
sensor_entry[key] = value

if "image" in self._inclusions and "image" not in sensor_entry:
sensor_entry["image"] = self._process_image(feed_entry)
if (
"link" in self._inclusions
and "link" not in sensor_entry
and (processed_link := self._process_link(feed_entry))
):
sensor_entry["link"] = processed_link
if "image" in self._inclusions and "image" not in sensor_entry:
sensor_entry["image"] = self._process_image(feed_entry)
if (
"link" in self._inclusions
and "link" not in sensor_entry
and (processed_link := self._process_link(feed_entry))
):
sensor_entry["link"] = processed_link
if self._remove_summary_image and "summary" in sensor_entry:
sensor_entry["summary"] = re.sub(
IMAGE_REGEX,
"",
sensor_entry["summary"],
)
_LOGGER.debug("Feed %s: Generated sensor entry: %s", self.name, sensor_entry)
return sensor_entry

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

def _process_image(self: FeedParserSensor, feed_entry: FeedParserDict) -> str:
if "enclosures" in feed_entry and feed_entry["enclosures"]:
if feed_entry.get("enclosures"):
images = [
enc for enc in feed_entry["enclosures"] if enc.type.startswith("image/")
]
Expand All @@ -248,7 +262,7 @@ def _process_image(self: FeedParserSensor, feed_entry: FeedParserDict) -> str:
return images[0]["href"]
elif "summary" in feed_entry:
images = re.findall(
r"<img.+?src=\"(.+?)\".+?>",
IMAGE_REGEX,
feed_entry["summary"],
)
if images:
Expand All @@ -265,7 +279,7 @@ def _process_link(self: FeedParserSensor, feed_entry: FeedParserDict) -> str:
"""Return link from feed entry."""
if "links" in feed_entry:
if len(feed_entry["links"]) > 1:
_LOGGER.warning(
_LOGGER.debug(
"Feed %s: More than one link found for %s. Using the first link.",
self.name,
feed_entry,
Expand Down
Loading