Skip to content

Commit

Permalink
Merge pull request #1 from jekalmin/add-config-entry
Browse files Browse the repository at this point in the history
add config entry
  • Loading branch information
jekalmin authored Dec 22, 2024
2 parents 67d93e7 + c177629 commit 5fdded6
Show file tree
Hide file tree
Showing 13 changed files with 758 additions and 189 deletions.
132 changes: 35 additions & 97 deletions custom_components/line_bot/__init__.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,32 @@
"""The line_bot component."""

import logging
import requests
import voluptuous as vol
from typing import Any, Dict
import homeassistant.helpers.config_validation as cv
from homeassistant.core import HomeAssistant, callback
from homeassistant.const import CONF_TOKEN
from urllib.parse import urlencode

from .http import async_register_http
from .const import (
DOMAIN, CONF_ALLOWED_CHAT_IDS, CONF_SECRET
)
from linebot import (
LineBotApi
)
from linebot.models import (
TextSendMessage,
ImageSendMessage,
StickerSendMessage,
TemplateSendMessage,
LocationSendMessage,
FlexSendMessage,
AudioSendMessage,
ButtonsTemplate,
ConfirmTemplate,
FlexSendMessage,
ImageSendMessage,
LocationSendMessage,
MessageAction,
PostbackAction,
StickerSendMessage,
TemplateSendMessage,
TextSendMessage,
URIAction,
MessageAction,
)

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType

from .const import DOMAIN
from .helpers import get_quota
from .http import async_register_http
from .services import async_setup_services

_LOGGER = logging.getLogger(__name__)

MESSAGES = {
Expand All @@ -38,89 +36,29 @@
"template": TemplateSendMessage,
"location": LocationSendMessage,
"flex": FlexSendMessage,
"audio": AudioSendMessage
"audio": AudioSendMessage,
}

CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.All(
vol.Schema(
{
vol.Required(CONF_TOKEN): cv.string,
vol.Optional(CONF_SECRET): cv.string,
vol.Optional(CONF_ALLOWED_CHAT_IDS): dict,
}
)
)
},
extra=vol.ALLOW_EXTRA,
)

def setup(hass: HomeAssistant, config: Dict[str, Any]):
if config[DOMAIN].get(CONF_SECRET):
async_register_http(hass, config)

lineClient = LineNotificationService(config[DOMAIN].get(CONF_TOKEN), config[DOMAIN].get(CONF_ALLOWED_CHAT_IDS, {}))
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up OpenAI Conversation."""
await async_setup_services(hass, config)
return True

def send_message(call):
to = call.data.get("to")
reply_token = call.data.get("reply_token")
message = call.data.get("message")
message_type = message.get("type")
lineClient.send_message(MESSAGES[message_type].new_from_json_dict(message), to=to, reply_token=reply_token)

def send_button_message(call):
to = call.data.get("to")
reply_token = call.data.get("reply_token")
text = call.data.get("text")
alt_text = call.data.get("alt_text", text)
buttons = call.data.get("buttons")
button_template = ButtonsTemplate(text=text, actions=to_actions(buttons))
lineClient.send_message(TemplateSendMessage(alt_text=alt_text, template=button_template), to=to, reply_token=reply_token)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up OpenAI Conversation from a config entry."""

def send_confirm_message(call):
to = call.data.get("to")
reply_token = call.data.get("reply_token")
text = call.data.get("text")
alt_text = call.data.get("altText", text)
buttons = call.data.get("buttons")
confirm_template = ConfirmTemplate(text=text, actions=to_actions(buttons))
lineClient.send_message(TemplateSendMessage(alt_text=alt_text, template=confirm_template), to=to, reply_token=reply_token)
await get_quota(hass, entry.data[CONF_ACCESS_TOKEN])
hass.data.setdefault(DOMAIN, {}).setdefault("entry", {}).setdefault(
entry.entry_id, {}
)

hass.services.register(DOMAIN, 'send_message', send_message)
hass.services.register(DOMAIN, 'send_button_message', send_button_message)
hass.services.register(DOMAIN, 'send_confirm_message', send_confirm_message)
async_register_http(hass)
return True

class LineNotificationService:
def __init__(self, token, allowed_chat_ids):
"""Initialize the service."""
self.allowed_chat_ids = allowed_chat_ids
self.line_bot_api = LineBotApi(token)

def send_message(self, message, **kwargs):
reply_token = kwargs.get("reply_token")
if reply_token:
self.line_bot_api.reply_message(reply_token, message)
else:
to = self.allowed_chat_ids.get(kwargs.get("to"))
self.line_bot_api.push_message(to, message)

def to_actions(buttons):
actions = []
for button in buttons:
action = None
text = button.get("text")
data = button.get("data")
uri = button.get("uri")
if data is not None:
label = button.get("label", text)
action = PostbackAction(label=label, data=data)
elif uri is not None:
label = button.get("label", text)
action = URIAction(label=label, uri=uri)
else:
label = button.get("label", text)
action = MessageAction(label=label, text=text)
actions.append(action)
return actions
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload OpenAI."""
hass.data[DOMAIN]["entry"].pop(entry.entry_id)
return True
215 changes: 215 additions & 0 deletions custom_components/line_bot/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
"""Config flow for Line Bot integration."""

from __future__ import annotations

import logging
from typing import Any

from linebot.exceptions import LineBotApiError
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_ACTION, CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.selector import (
SelectOptionDict,
SelectSelector,
SelectSelectorConfig,
)

from .const import (
CONF_ACTION_ADD_CHAT,
CONF_ACTION_REMOVE_CHAT,
CONF_ALLOWED_CHAT_IDS,
CONF_CHANNEL_SECRET,
CONF_CHAT_ID,
CONF_NEW_MESSAGES,
DOMAIN,
)
from .helpers import get_quota

_LOGGER = logging.getLogger(__name__)

CONF_ACTIONS = {
CONF_ACTION_ADD_CHAT: "Add a chat",
CONF_ACTION_REMOVE_CHAT: "Remove a chat",
}

STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_ACCESS_TOKEN): cv.string,
vol.Required(CONF_CHANNEL_SECRET): cv.string,
}
)


async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
"""Validate the user input allows us to connect.
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
"""
access_token = data[CONF_ACCESS_TOKEN]
await get_quota(hass, access_token)


class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for OpenAI Conversation."""

VERSION = 1

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA
)

errors = {}
try:
await validate_input(self.hass, user_input)
except LineBotApiError:
errors["base"] = "invalid_auth"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_create_entry(title="Line Bot", data=user_input)

return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)

@staticmethod
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> config_entries.OptionsFlow:
"""Create the options flow."""
return LineBotOptionsFlow()


class LineBotOptionsFlow(config_entries.OptionsFlow):
"""OpenAI config flow options handler."""

def __init__(self) -> None:
"""Initialize options flow."""
self.selected_chat = None

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage the options."""
if user_input is not None:
if user_input.get(CONF_ACTION) == "add_chat":
return await self.async_step_add_chat()
if user_input.get(CONF_ACTION) == "remove_chat":
return await self.async_step_remove_chat()

return self.async_show_form(
step_id="init",
data_schema=vol.Schema({vol.Required(CONF_ACTION): vol.In(CONF_ACTIONS)}),
)

async def async_step_add_chat(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Add a chat."""
self.selected_chat = None
errors = {}
if user_input is not None:
if user_input.get(CONF_NEW_MESSAGES) is not None:
self.selected_chat = user_input[CONF_NEW_MESSAGES]
return await self.async_step_configure_chat(user_input)

new_messages = self.get_new_messages()
return self.async_show_form(
step_id="add_chat",
errors=errors,
data_schema=vol.Schema(
{
vol.Required(CONF_NEW_MESSAGES): SelectSelector(
SelectSelectorConfig(
options=[
SelectOptionDict(
value=key,
label=f"{key[:5]} ({value.message.text})",
)
for key, value in new_messages.items()
],
)
),
}
),
)

async def async_step_configure_chat(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Configure a chat."""
chat_id = self.selected_chat
if user_input.get(CONF_NAME) is not None:
new_data = self.config_entry.data.copy()
allowed_chat_ids = new_data.get(CONF_ALLOWED_CHAT_IDS, {}).copy()
allowed_chat_ids.update(
{user_input.get(CONF_NAME): {CONF_CHAT_ID: chat_id}}
)
new_data[CONF_ALLOWED_CHAT_IDS] = allowed_chat_ids

self.get_new_messages().pop(chat_id)
self.hass.config_entries.async_update_entry(
self.config_entry,
data=new_data,
)
return self.async_create_entry(title="", data={})

return self.async_show_form(
step_id="configure_chat",
data_schema=vol.Schema({vol.Required(CONF_NAME): str}),
)

async def async_step_remove_chat(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Remove a chat."""
if user_input is not None:
new_data = self.config_entry.data.copy()
allowed_chat_ids = new_data.get(CONF_ALLOWED_CHAT_IDS, {})
new_allowed_chat_ids = {
key: value
for key, value in allowed_chat_ids.items()
if key not in user_input.get(CONF_ALLOWED_CHAT_IDS, [])
}
new_data[CONF_ALLOWED_CHAT_IDS] = new_allowed_chat_ids

self.hass.config_entries.async_update_entry(
self.config_entry,
data=new_data,
)
return self.async_create_entry(title="", data={})

allowed_chat_ids = list(
self.config_entry.data.get(CONF_ALLOWED_CHAT_IDS, {}).keys()
)

return self.async_show_form(
step_id="remove_chat",
data_schema=vol.Schema(
{
vol.Optional(CONF_ALLOWED_CHAT_IDS): SelectSelector(
SelectSelectorConfig(
options=allowed_chat_ids,
multiple=True,
)
),
}
),
)

def get_new_messages(self):
"""Get new messages."""
return next(iter(self.hass.data[DOMAIN]["entry"].values())).get(
CONF_NEW_MESSAGES, {}
)
16 changes: 11 additions & 5 deletions custom_components/line_bot/const.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
DOMAIN = 'line_bot'
"""Constants for the Line Bot."""

CONF_SECRET = 'secret'
CONF_ALLOWED_CHAT_IDS = 'allowed_chat_ids'
DOMAIN = "line_bot"

EVENT_WEBHOOK_TEXT_RECEIVED = 'line_webhook_text_received'
EVENT_WEBHOOK_POSTBACK_RECEIVED = 'line_webhook_postback_received'
CONF_ACTION_ADD_CHAT = "add_chat"
CONF_ACTION_REMOVE_CHAT = "remove_chat"
CONF_ALLOWED_CHAT_IDS = "allowed_chat_ids"
CONF_CHANNEL_SECRET = "channel_secret"
CONF_CHAT_ID = "chat_id"
CONF_NEW_MESSAGES = "new_messages"

EVENT_WEBHOOK_TEXT_RECEIVED = "line_webhook_text_received"
EVENT_WEBHOOK_POSTBACK_RECEIVED = "line_webhook_postback_received"
Loading

0 comments on commit 5fdded6

Please sign in to comment.