From f8e5165ec7fd749bb3fffafacb662ea9d4ba96a8 Mon Sep 17 00:00:00 2001 From: Andrew Jackson Date: Mon, 13 Apr 2026 16:18:34 +0100 Subject: [PATCH] Add quote approval policy to Mastodon post service (#168092) Co-authored-by: Erwin Douna Co-authored-by: Ariel Ebersberger <31776703+justanotherariel@users.noreply.github.com> --- homeassistant/components/mastodon/const.py | 1 + homeassistant/components/mastodon/services.py | 18 +++++++++++++++ .../components/mastodon/services.yaml | 8 +++++++ .../components/mastodon/strings.json | 11 ++++++++++ tests/components/mastodon/test_services.py | 22 +++++++++++++++++++ 5 files changed, 60 insertions(+) diff --git a/homeassistant/components/mastodon/const.py b/homeassistant/components/mastodon/const.py index 63d2ef7c66e..805c01ad762 100644 --- a/homeassistant/components/mastodon/const.py +++ b/homeassistant/components/mastodon/const.py @@ -15,6 +15,7 @@ DEFAULT_NAME: Final = "Mastodon" ATTR_ACCOUNT_NAME = "account_name" ATTR_STATUS = "status" ATTR_VISIBILITY = "visibility" +ATTR_QUOTE_APPROVAL_POLICY = "quote_approval_policy" ATTR_IDEMPOTENCY_KEY = "idempotency_key" ATTR_CONTENT_WARNING = "content_warning" ATTR_MEDIA_WARNING = "media_warning" diff --git a/homeassistant/components/mastodon/services.py b/homeassistant/components/mastodon/services.py index 5e93447fba5..018b91d80d3 100644 --- a/homeassistant/components/mastodon/services.py +++ b/homeassistant/components/mastodon/services.py @@ -52,6 +52,7 @@ from .const import ( ATTR_MEDIA_DESCRIPTION, ATTR_MEDIA_WARNING, ATTR_NOTE, + ATTR_QUOTE_APPROVAL_POLICY, ATTR_STATUS, ATTR_VALUE, ATTR_VISIBILITY, @@ -73,6 +74,14 @@ class StatusVisibility(StrEnum): DIRECT = "direct" +class QuoteApprovalPolicy(StrEnum): + """QuoteApprovalPolicy model.""" + + PUBLIC = "public" + FOLLOWERS = "followers" + NOBODY = "nobody" + + SERVICE_GET_ACCOUNT = "get_account" SERVICE_GET_ACCOUNT_SCHEMA = vol.Schema( { @@ -107,6 +116,9 @@ SERVICE_POST_SCHEMA = vol.Schema( vol.Required(ATTR_CONFIG_ENTRY_ID): str, vol.Required(ATTR_STATUS): str, vol.Optional(ATTR_VISIBILITY): vol.In([x.lower() for x in StatusVisibility]), + vol.Optional(ATTR_QUOTE_APPROVAL_POLICY): vol.In( + [x.lower() for x in QuoteApprovalPolicy] + ), vol.Optional(ATTR_IDEMPOTENCY_KEY): str, vol.Optional(ATTR_CONTENT_WARNING): str, vol.Optional(ATTR_LANGUAGE): str, @@ -287,6 +299,11 @@ async def _async_post(call: ServiceCall) -> ServiceResponse: if ATTR_VISIBILITY in call.data else None ) + quote_approval_policy: str | None = ( + QuoteApprovalPolicy(call.data[ATTR_QUOTE_APPROVAL_POLICY]) + if ATTR_QUOTE_APPROVAL_POLICY in call.data + else None + ) idempotency_key: str | None = call.data.get(ATTR_IDEMPOTENCY_KEY) spoiler_text: str | None = call.data.get(ATTR_CONTENT_WARNING) language: str | None = call.data.get(ATTR_LANGUAGE) @@ -307,6 +324,7 @@ async def _async_post(call: ServiceCall) -> ServiceResponse: client=client, status=status, visibility=visibility, + quote_approval_policy=quote_approval_policy, idempotency_key=idempotency_key, spoiler_text=spoiler_text, language=language, diff --git a/homeassistant/components/mastodon/services.yaml b/homeassistant/components/mastodon/services.yaml index ec637315c82..0fce29eff41 100644 --- a/homeassistant/components/mastodon/services.yaml +++ b/homeassistant/components/mastodon/services.yaml @@ -50,6 +50,14 @@ post: - private - direct translation_key: post_visibility + quote_approval_policy: + selector: + select: + options: + - public + - followers + - nobody + translation_key: quote_approval_policy idempotency_key: selector: text: diff --git a/homeassistant/components/mastodon/strings.json b/homeassistant/components/mastodon/strings.json index c63f9168627..720c0c71851 100644 --- a/homeassistant/components/mastodon/strings.json +++ b/homeassistant/components/mastodon/strings.json @@ -152,6 +152,13 @@ "public": "Public - Visible to everyone", "unlisted": "Unlisted - Public but not shown in public timelines" } + }, + "quote_approval_policy": { + "options": { + "followers": "Followers - Only accounts that follow you can quote this post", + "nobody": "Nobody - No one but you can quote this post", + "public": "Public - Anyone can quote this post" + } } }, "services": { @@ -222,6 +229,10 @@ "description": "If an image or video is attached, will mark the media as sensitive (default: no media warning).", "name": "Media warning" }, + "quote_approval_policy": { + "description": "Who can quote this post (default: account setting).\nIgnored if visibility is private or direct.", + "name": "Who can quote" + }, "status": { "description": "The status to post.", "name": "Status" diff --git a/tests/components/mastodon/test_services.py b/tests/components/mastodon/test_services.py index 28e688660f0..0aee7768fca 100644 --- a/tests/components/mastodon/test_services.py +++ b/tests/components/mastodon/test_services.py @@ -34,6 +34,7 @@ from homeassistant.components.mastodon.const import ( ATTR_MEDIA, ATTR_MEDIA_DESCRIPTION, ATTR_NOTE, + ATTR_QUOTE_APPROVAL_POLICY, ATTR_STATUS, ATTR_VISIBILITY, DOMAIN, @@ -378,6 +379,7 @@ async def test_unmute_account_failure_api_error( "status": "test toot", "spoiler_text": None, "visibility": None, + "quote_approval_policy": None, "idempotency_key": None, "language": None, "media_ids": None, @@ -390,6 +392,7 @@ async def test_unmute_account_failure_api_error( "status": "test toot", "spoiler_text": None, "visibility": "private", + "quote_approval_policy": None, "idempotency_key": None, "language": None, "media_ids": None, @@ -406,6 +409,7 @@ async def test_unmute_account_failure_api_error( "status": "test toot", "spoiler_text": "Spoiler", "visibility": "private", + "quote_approval_policy": None, "idempotency_key": None, "language": None, "media_ids": None, @@ -423,6 +427,7 @@ async def test_unmute_account_failure_api_error( "status": "test toot", "spoiler_text": "Spoiler", "visibility": None, + "quote_approval_policy": None, "idempotency_key": None, "language": "nl", "media_ids": "1", @@ -441,6 +446,7 @@ async def test_unmute_account_failure_api_error( "status": "test toot", "spoiler_text": "Spoiler", "visibility": None, + "quote_approval_policy": None, "idempotency_key": None, "language": "en", "media_ids": "1", @@ -454,6 +460,7 @@ async def test_unmute_account_failure_api_error( "language": "invalid-lang", "spoiler_text": None, "visibility": None, + "quote_approval_policy": None, "idempotency_key": None, "media_ids": None, "sensitive": None, @@ -470,6 +477,20 @@ async def test_unmute_account_failure_api_error( "language": None, "spoiler_text": None, "visibility": None, + "quote_approval_policy": None, + "media_ids": None, + "sensitive": None, + }, + ), + ( + {ATTR_STATUS: "test toot", ATTR_QUOTE_APPROVAL_POLICY: "followers"}, + { + "status": "test toot", + "spoiler_text": None, + "visibility": None, + "quote_approval_policy": "followers", + "idempotency_key": None, + "language": None, "media_ids": None, "sensitive": None, }, @@ -528,6 +549,7 @@ async def test_service_post( "status": "test toot", "spoiler_text": "Spoiler", "visibility": None, + "quote_approval_policy": None, "idempotency_key": None, "media_ids": "1", "media_description": None,