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

Mastodon post action #134788

Open
wants to merge 17 commits into
base: dev
Choose a base branch
from
Open
24 changes: 9 additions & 15 deletions homeassistant/components/mastodon/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@

from __future__ import annotations

from dataclasses import dataclass

from mastodon.Mastodon import Mastodon, MastodonError

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_ACCESS_TOKEN,
CONF_CLIENT_ID,
Expand All @@ -16,27 +13,24 @@
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import discovery
from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import slugify

from .const import CONF_BASE_URL, DOMAIN, LOGGER
from .coordinator import MastodonCoordinator
from .coordinator import MastodonConfigEntry, MastodonCoordinator, MastodonData
from .services import setup_services
from .utils import construct_mastodon_username, create_mastodon_client

PLATFORMS: list[Platform] = [Platform.NOTIFY, Platform.SENSOR]

CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)

@dataclass
class MastodonData:
"""Mastodon data type."""

client: Mastodon
instance: dict
account: dict
coordinator: MastodonCoordinator


type MastodonConfigEntry = ConfigEntry[MastodonData]
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Mastodon component."""
setup_services(hass)
return True


async def async_setup_entry(hass: HomeAssistant, entry: MastodonConfigEntry) -> bool:
Expand Down
7 changes: 7 additions & 0 deletions homeassistant/components/mastodon/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,10 @@
ACCOUNT_FOLLOWERS_COUNT: Final = "followers_count"
ACCOUNT_FOLLOWING_COUNT: Final = "following_count"
ACCOUNT_STATUSES_COUNT: Final = "statuses_count"

ATTR_CONFIG_ENTRY_ID = "config_entry_id"
ATTR_STATUS = "status"
ATTR_VISIBILITY = "visibility"
ATTR_CONTENT_WARNING = "content_warning"
ATTR_MEDIA_WARNING = "media_warning"
ATTR_MEDIA = "media"
15 changes: 15 additions & 0 deletions homeassistant/components/mastodon/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,33 @@

from __future__ import annotations

from dataclasses import dataclass
from datetime import timedelta
from typing import Any

from mastodon import Mastodon
from mastodon.Mastodon import MastodonError

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import LOGGER


@dataclass
class MastodonData:
"""Mastodon data type."""

client: Mastodon
instance: dict
account: dict
coordinator: MastodonCoordinator


type MastodonConfigEntry = ConfigEntry[MastodonData]


class MastodonCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Class to manage fetching Mastodon data."""

Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/mastodon/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,10 @@
"default": "mdi:message-text"
}
}
},
"services": {
"post": {
"service": "mdi:message-text"
}
}
}
68 changes: 46 additions & 22 deletions homeassistant/components/mastodon/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from __future__ import annotations

import mimetypes
from typing import Any, cast

from mastodon import Mastodon
Expand All @@ -16,15 +15,21 @@
)
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_CLIENT_ID, CONF_CLIENT_SECRET
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, issue_registry as ir
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

from .const import CONF_BASE_URL, DEFAULT_URL, LOGGER
from .const import (
ATTR_CONTENT_WARNING,
ATTR_MEDIA_WARNING,
CONF_BASE_URL,
DEFAULT_URL,
DOMAIN,
)
from .utils import get_media_type

ATTR_MEDIA = "media"
ATTR_TARGET = "target"
ATTR_MEDIA_WARNING = "media_warning"
ATTR_CONTENT_WARNING = "content_warning"

PLATFORM_SCHEMA = NOTIFY_PLATFORM_SCHEMA.extend(
{
Expand Down Expand Up @@ -67,6 +72,17 @@
def send_message(self, message: str = "", **kwargs: Any) -> None:
"""Toot a message, with media perhaps."""

ir.create_issue(
self.hass,
DOMAIN,
"deprecated_notify_action_mastodon",
breaks_in_ha_version="2025.8.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecated_notify_action",
)

target = None
if (target_list := kwargs.get(ATTR_TARGET)) is not None:
target = cast(list[str], target_list)[0]
Expand All @@ -82,8 +98,11 @@
media = data.get(ATTR_MEDIA)
if media:
if not self.hass.config.is_allowed_path(media):
LOGGER.warning("'%s' is not a whitelisted directory", media)
return
raise HomeAssistantError(

Check warning on line 101 in homeassistant/components/mastodon/notify.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/mastodon/notify.py#L101

Added line #L101 was not covered by tests
translation_domain=DOMAIN,
translation_key="not_whitelisted_directory",
translation_placeholders={"media": media},
)
mediadata = self._upload_media(media)

sensitive = data.get(ATTR_MEDIA_WARNING)
Expand All @@ -93,34 +112,39 @@
try:
self.client.status_post(
message,
media_ids=mediadata["id"],
sensitive=sensitive,
visibility=target,
spoiler_text=content_warning,
media_ids=mediadata["id"],
sensitive=sensitive,
)
except MastodonAPIError:
LOGGER.error("Unable to send message")
except MastodonAPIError as err:
raise HomeAssistantError(

Check warning on line 121 in homeassistant/components/mastodon/notify.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/mastodon/notify.py#L120-L121

Added lines #L120 - L121 were not covered by tests
translation_domain=DOMAIN,
translation_key="unable_to_send_message",
) from err

else:
try:
self.client.status_post(
message, visibility=target, spoiler_text=content_warning
)
except MastodonAPIError:
LOGGER.error("Unable to send message")
except MastodonAPIError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="unable_to_send_message",
) from err

def _upload_media(self, media_path: Any = None) -> Any:
"""Upload media."""
with open(media_path, "rb"):
media_type = self._media_type(media_path)
media_type = get_media_type(media_path)

Check warning on line 140 in homeassistant/components/mastodon/notify.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/mastodon/notify.py#L140

Added line #L140 was not covered by tests
try:
mediadata = self.client.media_post(media_path, mime_type=media_type)
except MastodonAPIError:
LOGGER.error(f"Unable to upload image {media_path}")
except MastodonAPIError as err:
raise HomeAssistantError(

Check warning on line 144 in homeassistant/components/mastodon/notify.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/mastodon/notify.py#L143-L144

Added lines #L143 - L144 were not covered by tests
translation_domain=DOMAIN,
translation_key="unable_to_upload_image",
translation_placeholders={"media_path": media_path},
) from err

return mediadata

def _media_type(self, media_path: Any = None) -> Any:
"""Get media Type."""
(media_type, _) = mimetypes.guess_type(media_path)

return media_type
8 changes: 4 additions & 4 deletions homeassistant/components/mastodon/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ rules:
action-exceptions:
status: todo
comment: |
Legacy Notify needs rewriting once Notify architecture stabilizes.
Awaiting legacy Notify deprecation.
config-entry-unloading: done
docs-configuration-parameters:
status: exempt
Expand All @@ -42,15 +42,15 @@ rules:
parallel-updates:
status: todo
comment: |
Does not set parallel-updates on notify platform.
Awaiting legacy Notify deprecation.
reauthentication-flow:
status: todo
comment: |
Waiting to move to oAuth.
test-coverage:
status: todo
comment: |
Legacy Notify needs rewriting once Notify architecture stabilizes.
Awaiting legacy Notify deprecation.

# Gold
devices: done
Expand Down Expand Up @@ -78,7 +78,7 @@ rules:
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: todo
exception-translations: done
icon-translations: done
reconfiguration-flow:
status: todo
Expand Down
Loading