Skip to content

Commit

Permalink
Add get_tasks action
Browse files Browse the repository at this point in the history
  • Loading branch information
tr4nt0r committed Oct 5, 2024
1 parent 85a9a8e commit d551596
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 2 deletions.
109 changes: 107 additions & 2 deletions homeassistant/components/habitica/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from http import HTTPStatus
import logging
from typing import cast

from aiohttp import ClientResponseError
from habitipy.aio import HabitipyAsync
Expand All @@ -18,28 +19,43 @@
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, 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_KEYWORD,
ATTR_PATH,
ATTR_PRIORITY,
ATTR_TAG,
ATTR_TASK,
ATTR_TYPE,
CONF_API_USER,
DEFAULT_URL,
DOMAIN,
EVENT_API_CALL_SUCCESS,
PRIORITIES,
SERVICE_API_CALL,
SERVICE_GET_TASKS,
)
from .coordinator import HabiticaDataUpdateCoordinator

_LOGGER = logging.getLogger(__name__)

type HabiticaConfigEntry = ConfigEntry[HabiticaDataUpdateCoordinator]


SENSORS_TYPES = ["name", "hp", "maxHealth", "mp", "maxMP", "exp", "toNextLevel", "lvl"]

INSTANCE_SCHEMA = vol.All(
Expand Down Expand Up @@ -92,6 +108,20 @@ def has_all_unique_users_names(value):
vol.Optional(ATTR_ARGS): dict,
}
)
SERVICE_GET_TASKS_SCHEMA = vol.Schema(
{
vol.Required(ATTR_CONFIG_ENTRY): ConfigEntrySelector(),
vol.Optional(ATTR_TYPE): vol.All(
cv.ensure_list, [vol.In({"habit", "daily", "reward", "todo"})]
),
vol.Optional(ATTR_PRIORITY): vol.All(
cv.ensure_list, [vol.In(set(PRIORITIES.keys()))]
),
vol.Optional(ATTR_TASK): vol.All(cv.ensure_list, [str]),
vol.Optional(ATTR_TAG): vol.All(cv.ensure_list, [str]),
vol.Optional(ATTR_KEYWORD): str,
}
)


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
Expand All @@ -108,6 +138,81 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
)
)

async def get_tasks(call: ServiceCall) -> ServiceResponse:
"""Skill action."""
entry: HabiticaConfigEntry | None

if not (

Check warning on line 145 in homeassistant/components/habitica/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/habitica/__init__.py#L145

Added line #L145 was not covered by tests
entry := hass.config_entries.async_get_entry(call.data[ATTR_CONFIG_ENTRY])
):
raise ServiceValidationError(

Check warning on line 148 in homeassistant/components/habitica/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/habitica/__init__.py#L148

Added line #L148 was not covered by tests
translation_domain=DOMAIN,
translation_key="entry_not_found",
)

coordinator = entry.runtime_data

Check warning on line 153 in homeassistant/components/habitica/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/habitica/__init__.py#L153

Added line #L153 was not covered by tests

response = coordinator.data.tasks

Check warning on line 155 in homeassistant/components/habitica/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/habitica/__init__.py#L155

Added line #L155 was not covered by tests

if types := call.data.get(ATTR_TYPE):
response = [task for task in response if task["type"] in types]

Check warning on line 158 in homeassistant/components/habitica/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/habitica/__init__.py#L157-L158

Added lines #L157 - L158 were not covered by tests

if priority := call.data.get(ATTR_PRIORITY):
priority = [PRIORITIES[k] for k in priority]
response = [

Check warning on line 162 in homeassistant/components/habitica/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/habitica/__init__.py#L160-L162

Added lines #L160 - L162 were not covered by tests
task
for task in response
if task.get("priority") is None or task.get("priority") in priority
]

if tasks := call.data.get(ATTR_TASK):
response = [

Check warning on line 169 in homeassistant/components/habitica/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/habitica/__init__.py#L168-L169

Added lines #L168 - L169 were not covered by tests
task
for task in response
if task["id"] in tasks
or task.get("alias") in tasks
or task["text"] in tasks
]

if tags := call.data.get(ATTR_TAG):
tag_ids = {

Check warning on line 178 in homeassistant/components/habitica/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/habitica/__init__.py#L177-L178

Added lines #L177 - L178 were not covered by tests
tag["id"]
for tag in coordinator.data.user.get("tags", [])
if tag["name"].lower()
in (tag.lower() for tag in tags) # Case-insensitive matching
}

response = [

Check warning on line 185 in homeassistant/components/habitica/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/habitica/__init__.py#L185

Added line #L185 was not covered by tests
task
for task in response
if any(tag_id in task.get("tags", []) for tag_id in tag_ids)
]
if keyword := call.data.get(ATTR_KEYWORD):
keyword = keyword.lower()
response = [

Check warning on line 192 in homeassistant/components/habitica/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/habitica/__init__.py#L190-L192

Added lines #L190 - L192 were not covered by tests
task
for task in response
if keyword in task["text"].lower()
or keyword in task["notes"].lower()
or any(
keyword in item["text"].lower()
for item in task.get("checklist", [])
)
]
return cast(

Check warning on line 202 in homeassistant/components/habitica/__init__.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/habitica/__init__.py#L202

Added line #L202 was not covered by tests
ServiceResponse,
{
"tasks": response,
},
)

hass.services.async_register(
DOMAIN,
SERVICE_GET_TASKS,
get_tasks,
schema=SERVICE_GET_TASKS_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
return True


Expand Down
11 changes: 11 additions & 0 deletions homeassistant/components/habitica/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,14 @@
NAME = "Habitica"

UNIT_TASKS = "tasks"

ATTR_CONFIG_ENTRY = "config_entry"
ATTR_TASK = "task"
ATTR_TYPE = "type"
ATTR_PRIORITY = "priority"
ATTR_TAG = "tag"
ATTR_KEYWORD = "keyword"

SERVICE_GET_TASKS = "get_tasks"

PRIORITIES = {"trivial": 0.1, "easy": 1, "medium": 1.5, "hard": 2}
6 changes: 6 additions & 0 deletions homeassistant/components/habitica/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@
"services": {
"api_call": {
"service": "mdi:console"
},
"get_tasks": {
"service": "mdi:calendar-export",
"sections": {
"filter": "mdi:calendar-filter"
}
}
}
}
50 changes: 50 additions & 0 deletions homeassistant/components/habitica/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,53 @@ api_call:
example: '{"text": "Use API from Home Assistant", "type": "todo"}'
selector:
object:
get_tasks:
fields:
config_entry:
required: true
selector:
config_entry:
integration: habitica
filter:
collapsed: true
fields:
type:
required: false
selector:
select:
options:
- "habit"
- "daily"
- "todo"
- "reward"
mode: dropdown
translation_key: "type"
multiple: true
sort: true
priority:
required: false
selector:
select:
options:
- "trivial"
- "easy"
- "medium"
- "hard"
mode: dropdown
translation_key: "priority"
multiple: true
sort: false
task:
required: false
selector:
text:
multiple: true
tag:
required: false
selector:
text:
multiple: true
keyword:
required: false
selector:
text:
57 changes: 57 additions & 0 deletions homeassistant/components/habitica/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@
},
"service_call_exception": {
"message": "Unable to connect to Habitica, try again later"
},
"entry_not_found": {
"message": "The selected character is currently not configured or loaded in Home Assistant."
}
},
"issues": {
Expand All @@ -174,6 +177,60 @@
"description": "Any additional JSON or URL parameter arguments. See apidoc mentioned for path. Example uses same API endpoint."
}
}
},
"get_tasks": {
"name": "Get tasks",
"description": "Retrieve tasks from your Habitica character.",
"fields": {
"config_entry": {
"name": "Select character",
"description": "Choose the Habitica character to retrieve tasks from."
},
"type": {
"name": "Task type",
"description": "Filter tasks by type."
},
"priority": {
"name": "Difficulty",
"description": "Filter tasks by difficulty."
},
"task": {
"name": "Task name",
"description": "Select tasks by matching their name (or task ID)."
},
"tag": {
"name": "Tag",
"description": "Filter tasks that have one or more of the selected tags."
},
"keyword": {
"name": "Keyword",
"description": "Filter tasks by keyword, searching across titles, notes, and checklists."
}
},
"sections": {
"filter": {
"name": "Filter options",
"description": "Use the optional filters to narrow the returned tasks."
}
}
}
},
"selector": {
"type": {
"options": {
"daily": "Daily",
"habit": "Habit",
"todo": "To-do",
"reward": "Reward"
}
},
"priority": {
"options": {
"trivial": "Trivial",
"easy": "Easy",
"medium": "Medium",
"hard": "Hard"
}
}
}
}

0 comments on commit d551596

Please sign in to comment.