Skip to content

Commit

Permalink
Add post action to mastodon (#134788)
Browse files Browse the repository at this point in the history
Co-authored-by: Joost Lekkerkerker <[email protected]>
  • Loading branch information
andrew-codechimp and joostlek authored Jan 31, 2025
1 parent a7903d3 commit 50f3d79
Show file tree
Hide file tree
Showing 12 changed files with 607 additions and 41 deletions.
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 __init__(
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.9.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 @@ def send_message(self, message: str = "", **kwargs: Any) -> None:
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(
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 @@ def send_message(self, message: str = "", **kwargs: Any) -> None:
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(
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)
try:
mediadata = self.client.media_post(media_path, mime_type=media_type)
except MastodonAPIError:
LOGGER.error("Unable to upload image %s", media_path)
except MastodonAPIError as err:
raise HomeAssistantError(
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

0 comments on commit 50f3d79

Please sign in to comment.