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
5 changed files with 168 additions and 2 deletions
|
@ -2,6 +2,7 @@
|
|||
|
||||
from http import HTTPStatus
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientResponseError
|
||||
from habitipy.aio import HabitipyAsync
|
||||
|
@ -18,21 +19,35 @@ from homeassistant.const import (
|
|||
CONF_VERIFY_SSL,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.core import (
|
||||
HomeAssistant,
|
||||
ServiceCall,
|
||||
ServiceResponse,
|
||||
SupportsResponse,
|
||||
)
|
||||
from homeassistant.exceptions import (
|
||||
ConfigEntryNotReady,
|
||||
HomeAssistantError,
|
||||
ServiceValidationError,
|
||||
)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.selector import ConfigEntrySelector
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
ATTR_ARGS,
|
||||
ATTR_CONFIG_ENTRY,
|
||||
ATTR_DATA,
|
||||
ATTR_PATH,
|
||||
ATTR_SKILL,
|
||||
ATTR_TASK,
|
||||
CONF_API_USER,
|
||||
DEFAULT_URL,
|
||||
DOMAIN,
|
||||
EVENT_API_CALL_SUCCESS,
|
||||
SERVICE_API_CALL,
|
||||
SERVICE_CAST_SKILL,
|
||||
)
|
||||
from .coordinator import HabiticaDataUpdateCoordinator
|
||||
|
||||
|
@ -92,6 +107,13 @@ SERVICE_API_CALL_SCHEMA = vol.Schema(
|
|||
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:
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -21,3 +21,8 @@ MANUFACTURER = "HabitRPG, Inc."
|
|||
NAME = "Habitica"
|
||||
|
||||
UNIT_TASKS = "tasks"
|
||||
|
||||
ATTR_CONFIG_ENTRY = "config_entry"
|
||||
ATTR_SKILL = "skill"
|
||||
ATTR_TASK = "task"
|
||||
SERVICE_CAST_SKILL = "cast_skill"
|
||||
|
|
|
@ -96,6 +96,9 @@
|
|||
"services": {
|
||||
"api_call": {
|
||||
"service": "mdi:console"
|
||||
},
|
||||
"cast_skill": {
|
||||
"service": "mdi:creation-outline"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,3 +15,25 @@ api_call:
|
|||
example: '{"text": "Use API from Home Assistant", "type": "todo"}'
|
||||
selector:
|
||||
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": {
|
||||
"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": {
|
||||
|
@ -180,6 +192,34 @@
|
|||
"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…
Add table
Reference in a new issue