mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 11:47:06 +00:00
Add cast skill action to Habitica integration (#127000)
* Add cast skill action for task skills * exceptions * task not found exception * request refresh to update mana/xp sensors * Changes * remove service_call prefix * fixes
This commit is contained in:
parent
546d0b25b0
commit
3e8bc98f23
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp import ClientResponseError
|
from aiohttp import ClientResponseError
|
||||||
from habitipy.aio import HabitipyAsync
|
from habitipy.aio import HabitipyAsync
|
||||||
@ -18,21 +19,35 @@ from homeassistant.const import (
|
|||||||
CONF_VERIFY_SSL,
|
CONF_VERIFY_SSL,
|
||||||
Platform,
|
Platform,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall
|
from homeassistant.core import (
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
HomeAssistant,
|
||||||
|
ServiceCall,
|
||||||
|
ServiceResponse,
|
||||||
|
SupportsResponse,
|
||||||
|
)
|
||||||
|
from homeassistant.exceptions import (
|
||||||
|
ConfigEntryNotReady,
|
||||||
|
HomeAssistantError,
|
||||||
|
ServiceValidationError,
|
||||||
|
)
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.selector import ConfigEntrySelector
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_ARGS,
|
ATTR_ARGS,
|
||||||
|
ATTR_CONFIG_ENTRY,
|
||||||
ATTR_DATA,
|
ATTR_DATA,
|
||||||
ATTR_PATH,
|
ATTR_PATH,
|
||||||
|
ATTR_SKILL,
|
||||||
|
ATTR_TASK,
|
||||||
CONF_API_USER,
|
CONF_API_USER,
|
||||||
DEFAULT_URL,
|
DEFAULT_URL,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
EVENT_API_CALL_SUCCESS,
|
EVENT_API_CALL_SUCCESS,
|
||||||
SERVICE_API_CALL,
|
SERVICE_API_CALL,
|
||||||
|
SERVICE_CAST_SKILL,
|
||||||
)
|
)
|
||||||
from .coordinator import HabiticaDataUpdateCoordinator
|
from .coordinator import HabiticaDataUpdateCoordinator
|
||||||
|
|
||||||
@ -92,6 +107,13 @@ SERVICE_API_CALL_SCHEMA = vol.Schema(
|
|||||||
vol.Optional(ATTR_ARGS): dict,
|
vol.Optional(ATTR_ARGS): dict,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
SERVICE_CAST_SKILL_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(ATTR_CONFIG_ENTRY): ConfigEntrySelector(),
|
||||||
|
vol.Required(ATTR_SKILL): cv.string,
|
||||||
|
vol.Optional(ATTR_TASK): cv.string,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
@ -108,6 +130,80 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def cast_skill(call: ServiceCall) -> ServiceResponse:
|
||||||
|
"""Skill action."""
|
||||||
|
entry: HabiticaConfigEntry | None
|
||||||
|
if not (
|
||||||
|
entry := hass.config_entries.async_get_entry(call.data[ATTR_CONFIG_ENTRY])
|
||||||
|
):
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="entry_not_found",
|
||||||
|
)
|
||||||
|
coordinator = entry.runtime_data
|
||||||
|
skill = {
|
||||||
|
"pickpocket": {"spellId": "pickPocket", "cost": "10 MP"},
|
||||||
|
"backstab": {"spellId": "backStab", "cost": "15 MP"},
|
||||||
|
"smash": {"spellId": "smash", "cost": "10 MP"},
|
||||||
|
"fireball": {"spellId": "fireball", "cost": "10 MP"},
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
task_id = next(
|
||||||
|
task["id"]
|
||||||
|
for task in coordinator.data.tasks
|
||||||
|
if call.data[ATTR_TASK] in (task["id"], task.get("alias"))
|
||||||
|
or call.data[ATTR_TASK] == task["text"]
|
||||||
|
)
|
||||||
|
except StopIteration as e:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="task_not_found",
|
||||||
|
translation_placeholders={"task": f"'{call.data[ATTR_TASK]}'"},
|
||||||
|
) from e
|
||||||
|
|
||||||
|
try:
|
||||||
|
response: dict[str, Any] = await coordinator.api.user.class_.cast[
|
||||||
|
skill[call.data[ATTR_SKILL]]["spellId"]
|
||||||
|
].post(targetId=task_id)
|
||||||
|
except ClientResponseError as e:
|
||||||
|
if e.status == HTTPStatus.TOO_MANY_REQUESTS:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="setup_rate_limit_exception",
|
||||||
|
) from e
|
||||||
|
if e.status == HTTPStatus.UNAUTHORIZED:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="not_enough_mana",
|
||||||
|
translation_placeholders={
|
||||||
|
"cost": skill[call.data[ATTR_SKILL]]["cost"],
|
||||||
|
"mana": f"{int(coordinator.data.user.get("stats", {}).get("mp", 0))} MP",
|
||||||
|
},
|
||||||
|
) from e
|
||||||
|
if e.status == HTTPStatus.NOT_FOUND:
|
||||||
|
# could also be task not found, but the task is looked up
|
||||||
|
# before the request, so most likely wrong skill selected
|
||||||
|
# or the skill hasn't been unlocked yet.
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="skill_not_found",
|
||||||
|
translation_placeholders={"skill": call.data[ATTR_SKILL]},
|
||||||
|
) from e
|
||||||
|
raise HomeAssistantError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="service_call_exception",
|
||||||
|
) from e
|
||||||
|
else:
|
||||||
|
await coordinator.async_request_refresh()
|
||||||
|
return response
|
||||||
|
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_CAST_SKILL,
|
||||||
|
cast_skill,
|
||||||
|
schema=SERVICE_CAST_SKILL_SCHEMA,
|
||||||
|
supports_response=SupportsResponse.ONLY,
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,3 +21,8 @@ MANUFACTURER = "HabitRPG, Inc."
|
|||||||
NAME = "Habitica"
|
NAME = "Habitica"
|
||||||
|
|
||||||
UNIT_TASKS = "tasks"
|
UNIT_TASKS = "tasks"
|
||||||
|
|
||||||
|
ATTR_CONFIG_ENTRY = "config_entry"
|
||||||
|
ATTR_SKILL = "skill"
|
||||||
|
ATTR_TASK = "task"
|
||||||
|
SERVICE_CAST_SKILL = "cast_skill"
|
||||||
|
@ -96,6 +96,9 @@
|
|||||||
"services": {
|
"services": {
|
||||||
"api_call": {
|
"api_call": {
|
||||||
"service": "mdi:console"
|
"service": "mdi:console"
|
||||||
|
},
|
||||||
|
"cast_skill": {
|
||||||
|
"service": "mdi:creation-outline"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,3 +15,25 @@ api_call:
|
|||||||
example: '{"text": "Use API from Home Assistant", "type": "todo"}'
|
example: '{"text": "Use API from Home Assistant", "type": "todo"}'
|
||||||
selector:
|
selector:
|
||||||
object:
|
object:
|
||||||
|
cast_skill:
|
||||||
|
fields:
|
||||||
|
config_entry:
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
config_entry:
|
||||||
|
integration: habitica
|
||||||
|
skill:
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
select:
|
||||||
|
options:
|
||||||
|
- "pickpocket"
|
||||||
|
- "backstab"
|
||||||
|
- "smash"
|
||||||
|
- "fireball"
|
||||||
|
mode: dropdown
|
||||||
|
translation_key: "skill_select"
|
||||||
|
task:
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
@ -154,6 +154,18 @@
|
|||||||
},
|
},
|
||||||
"service_call_exception": {
|
"service_call_exception": {
|
||||||
"message": "Unable to connect to Habitica, try again later"
|
"message": "Unable to connect to Habitica, try again later"
|
||||||
|
},
|
||||||
|
"not_enough_mana": {
|
||||||
|
"message": "Unable to cast skill, not enough mana. Your character has {mana}, but the skill costs {cost}."
|
||||||
|
},
|
||||||
|
"skill_not_found": {
|
||||||
|
"message": "Unable to cast skill, your character does not have the skill or spell {skill}."
|
||||||
|
},
|
||||||
|
"entry_not_found": {
|
||||||
|
"message": "The selected character is currently not configured or loaded in Home Assistant."
|
||||||
|
},
|
||||||
|
"task_not_found": {
|
||||||
|
"message": "Unable to cast skill, could not find the task {task}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"issues": {
|
"issues": {
|
||||||
@ -180,6 +192,34 @@
|
|||||||
"description": "Any additional JSON or URL parameter arguments. See apidoc mentioned for path. Example uses same API endpoint."
|
"description": "Any additional JSON or URL parameter arguments. See apidoc mentioned for path. Example uses same API endpoint."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"cast_skill": {
|
||||||
|
"name": "Cast a skill",
|
||||||
|
"description": "Use a skill or spell from your Habitica character on a specific task to affect its progress or status.",
|
||||||
|
"fields": {
|
||||||
|
"config_entry": {
|
||||||
|
"name": "Select character",
|
||||||
|
"description": "Choose the Habitica character to cast the skill."
|
||||||
|
},
|
||||||
|
"skill": {
|
||||||
|
"name": "Skill",
|
||||||
|
"description": "Select the skill or spell you want to cast on the task. Only skills corresponding to your character's class can be used."
|
||||||
|
},
|
||||||
|
"task": {
|
||||||
|
"name": "Task name",
|
||||||
|
"description": "The name (or task ID) of the task you want to target with the skill or spell."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"skill_select": {
|
||||||
|
"options": {
|
||||||
|
"fireball": "Mage: Burst of flames",
|
||||||
|
"pickpocket": "Rogue: Pickpocket",
|
||||||
|
"backstab": "Rogue: Backstab",
|
||||||
|
"smash": "Warrior: Brutal smash"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user