From 3c07fca80182b7ed4f38b614cdd130b3904c96d6 Mon Sep 17 00:00:00 2001 From: sayam93 <163408168+sayam93@users.noreply.github.com> Date: Sun, 15 Dec 2024 16:26:07 +0530 Subject: [PATCH] feat: Add Integration through UI without breaking old config Allow users to add this integration using UI without breaking old configuration. Any notify service previously created through manual code in configuration.yaml remains functional. It gives the user choice to setup through UI if they want or continue with the old method. While setup through UI, the user enters the Whatsapp number (without country code) and country code separately. If the user's number is '+919876556789' where country code is '+91', then the user shall to enter 9876556789 as the WhatsApp Phone Number and +91 as the Country Code during setup. A notification service name notify.whatspie_919876556789 will be saved in such case. When calling the notify service (like notify.whatspie_919876556789), the user has to mention the full phone number of the recipient with country code. An updated readme is also available for users. --- README.md | 119 ++++++++- custom_components/whatspie/__init__.py | 34 ++- custom_components/whatspie/config_flow.py | 206 ++++++++++++++++ custom_components/whatspie/const.py | 8 + custom_components/whatspie/manifest.json | 9 +- custom_components/whatspie/notify.py | 230 +++++++++++++++--- custom_components/whatspie/services.yaml | 10 + .../whatspie/translations/en.json | 49 ++++ 8 files changed, 625 insertions(+), 40 deletions(-) mode change 100644 => 100755 custom_components/whatspie/__init__.py create mode 100755 custom_components/whatspie/config_flow.py create mode 100755 custom_components/whatspie/const.py mode change 100644 => 100755 custom_components/whatspie/manifest.json mode change 100644 => 100755 custom_components/whatspie/notify.py create mode 100755 custom_components/whatspie/services.yaml create mode 100755 custom_components/whatspie/translations/en.json diff --git a/README.md b/README.md index 1029800..1d06c5f 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,83 @@ # homeassistant-whatspie-integration -Send Home Assistant notifications to WhatsApp using [WhatsPie](https://whatspie.com/) + +Send Home Assistant notifications to WhatsApp using [WhatsPie](https://whatspie.com/). + +## Features +- Send WhatsApp messages directly from Home Assistant. +- UI-based configuration for easy setup. +- Backward compatibility with YAML configuration. +- Supports sending messages to multiple recipients. +- Options flow to update configurations without reinstallation. +- Detailed error handling and logging for troubleshooting. + +--- ## Installation -- Copy `custom_components/whatspie` directory into Home Assistant's `config/custom_components/` -- Create a new notification service in your `configuration.yaml` file: + +### Manual Installation +1. Copy the `custom_components/whatspie` directory into Home Assistant's `config/custom_components/`. +2. Restart Home Assistant. + +### HACS Installation +1. Add this GitHub repository as a custom repository in HACS: [HACS Custom Repositories](https://hacs.xyz/docs/faq/custom_repositories). +2. Install the **WhatsPie** integration via HACS. +3. Restart Home Assistant. + +--- + +## Configuration + +### Configuration via UI (Recommended) +1. Go to **Settings** > **Devices & Services** in Home Assistant. +2. Click **Add Integration** and search for **WhatsPie**. +3. Enter the required details: + - **API Token**: Your WhatsPie API token. + - **From Number**: Your WhatsPie phone number (without 0 or country code). + - **Country Code**: Your country code prefix (e.g., `+62` for Indonesia). +4. Click **Submit** to complete the setup. + +To modify settings, go to the **WhatsPie Integration** in the UI. + +--- + +### YAML Configuration (Legacy) +You can still use YAML for setup if preferred. Add the following to your `configuration.yaml`: + ```yaml notify: - name: send_wa platform: whatspie api_token: "" - from_number: "" - country_code: "" + from_number: "" + country_code: "" ``` -- Restart Home Assistant: Go to "Developer Tools", then press "Check Configuration" followed by "Restart" -- Alternatively, you can install this integration from HACS by adding this github repository as a custom repository: https://hacs.xyz/docs/faq/custom_repositories/ +Restart Home Assistant after updating `configuration.yaml`. + +--- ## Usage -Example automation configuration: +### Example Automation Configuration + +#### Using the Notify Service created using UI +``` +alias: Send Test Notification +description: "" +trigger: [] +condition: [] +action: + - action: notify.whatspie_621122334455 + data: + message: Test Notification -- HomeAssistant + target: + - "+621122335555" +mode: single +``` + +##### In target, change the number with the recepient number + +#### Using the Notify Service configured using YAML ```yaml alias: Send Test Notification description: "" @@ -32,3 +91,47 @@ action: - "+621122334455" mode: single ``` + +#### With Media Attachments (YAML only and if supported by WhatsPie API) +```yaml +alias: Send Media Notification +description: "" +trigger: [] +condition: [] +action: + - service: notify.send_wa + data: + message: "Test Notification with media" + target: + - "+621122334455" + data: + media_url: "https://example.com/path/to/image.jpg" +mode: single +``` + +--- + +## Advanced Features + +### Updating Configuration created in UI via Options Flow +- Navigate to **Settings** > **Devices & Services** > **WhatsPie Integration** > **Configure**. +- Update your API token, phone number, or country code directly from the UI. + +### Logging +- Check Home Assistant logs for detailed debug information in case of issues: + - API responses and errors. + - Connection issues with WhatsPie API. + +--- + +## Troubleshooting +- **Invalid API Token**: Ensure the API token is correctly copied from your WhatsPie account. +- **Connection Issues**: Verify network connectivity and that the WhatsPie API is accessible. +- **Message Not Delivered**: Check the phone number format, including country code. + +--- + +## Contributions +Contributions, issues, and feature requests are welcome! Please check the [issue tracker](https://github.com/arifwn/homeassistant-whatspie-integration/issues) for existing issues or create a new one. + +--- diff --git a/custom_components/whatspie/__init__.py b/custom_components/whatspie/__init__.py old mode 100644 new mode 100755 index e36b175..5efc981 --- a/custom_components/whatspie/__init__.py +++ b/custom_components/whatspie/__init__.py @@ -1 +1,33 @@ -"""Send notifications via WhatsPie""" +import logging + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.typing import ConfigType +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass: HomeAssistant, config: ConfigType): + """Set up the integration using YAML (deprecated).""" + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up Whatspie integration from a config entry.""" + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][entry.entry_id] = entry.data + + # Forward the entry setup to the notify platform + _LOGGER.debug("Forwarding setup to notify platform for WhatsPie integration.") + await hass.config_entries.async_forward_entry_setups(entry, ["notify"]) + _LOGGER.debug("Setup forwarded to notify platform successfully.") + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + await hass.config_entries.async_forward_entry_unload(entry, "notify") + hass.data[DOMAIN].pop(entry.entry_id) + return True diff --git a/custom_components/whatspie/config_flow.py b/custom_components/whatspie/config_flow.py new file mode 100755 index 0000000..5036707 --- /dev/null +++ b/custom_components/whatspie/config_flow.py @@ -0,0 +1,206 @@ +import voluptuous as vol +import httpx +import logging +import ssl +import certifi + +from homeassistant import config_entries +from homeassistant.core import HomeAssistant +from homeassistant.core import callback +from homeassistant.const import CONF_API_KEY +import homeassistant.helpers.config_validation as cv +from typing import Optional, Dict + +from .const import ( + DOMAIN, + CONF_API_TOKEN, + CONF_FROM_NUMBER, + CONF_COUNTRY_CODE, + CONF_ORIG_FROM_NUMBER, + CONF_ORIG_COUNTRY_CODE, +) + +_LOGGER = logging.getLogger(__name__) + + +class WhatsPieConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for WhatsPie integration.""" + + VERSION = 1 + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + errors = {} + + if user_input is not None: + try: + # Sanitize country_code and from_number + country_code = user_input[CONF_COUNTRY_CODE].lstrip("+") + from_number = user_input[CONF_FROM_NUMBER].lstrip( + "0" + ) # Remove leading "0" if present + + # Reassign sanitized values back to user_input + user_input[CONF_COUNTRY_CODE] = country_code + user_input[CONF_FROM_NUMBER] = from_number + + full_number = f"{country_code}{from_number}" + + await self._test_credentials( + user_input[CONF_API_TOKEN], + full_number, + ) + + user_input[CONF_ORIG_COUNTRY_CODE] = f"+{country_code}" + user_input[CONF_ORIG_FROM_NUMBER] = user_input[CONF_FROM_NUMBER] + user_input[CONF_COUNTRY_CODE] = f"{country_code}" + user_input[CONF_FROM_NUMBER] = full_number + + _LOGGER.debug("Creating entry with sanitized values: %s", user_input) + + return self.async_create_entry( + title="WhatsPie Notification", + data=user_input, + ) + except ValueError as err: + if "Connection error" in str(err): + errors["base"] = "connection_error" + elif "API validation error" in str(err): + errors["base"] = "auth_error" + else: + errors["base"] = "unknown_error" + except Exception: + errors["base"] = "auth_error" + + data_schema = vol.Schema( + { + vol.Required(CONF_API_TOKEN): cv.string, + vol.Required(CONF_FROM_NUMBER): cv.string, + vol.Required(CONF_COUNTRY_CODE): cv.string, + } + ) + + return self.async_show_form( + step_id="user", data_schema=data_schema, errors=errors + ) + + async def _test_credentials(self, api_token, full_number): + """Test the credentials by making an API call.""" + + url = "https://api.whatspie.com/messages" + headers = { + "Authorization": f"Bearer {api_token}", + "Content-Type": "application/json", + } + payload = { + "receiver": full_number, # Message self + "device": full_number, + "message": "Test message from Home Assistant - WhatsPie Integration", + "type": "chat", + "simulate_typing": 0, + } + + _LOGGER.debug("Testing credentials with WhatsPie API at %s", url) + + def create_ssl_context(): + """Create SSL context (blocking).""" + return ssl.create_default_context(cafile=certifi.where()) + + ssl_context = await self.hass.async_add_executor_job(create_ssl_context) + + try: + async with httpx.AsyncClient(verify=ssl_context) as client: + response = await client.post(url, json=payload, headers=headers) + + if response.status_code != 200: + _LOGGER.error( + "WhatsPie API validation failed: %s - %s", + response.status_code, + response.text, + ) + raise ValueError( + f"API validation error: {response.status_code} - {response.text}" + ) + + except httpx.RequestError as err: + _LOGGER.error("Connection error during WhatsPie validation: %s", err) + raise ValueError(f"Connection error: {err}") from err + except Exception as err: + _LOGGER.error("Unexpected error during WhatsPie validation: %s", err) + raise ValueError(f"Unexpected error: {err}") from err + + _LOGGER.debug("WhatsPie credentials validated successfully.") + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Define the options flow.""" + return WhatsPieOptionsFlowHandler(config_entry) + + +class WhatsPieOptionsFlowHandler(config_entries.OptionsFlow): + """Handle options flow for WhatsPie.""" + + def __init__(self, config_entry): + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Manage the options.""" + errors = {} + + if user_input is not None: + # Add validation here if necessary + if not user_input[CONF_ORIG_FROM_NUMBER].isdigit(): + errors["base"] = "invalid_phone_number" + elif not user_input[CONF_ORIG_COUNTRY_CODE]: + errors["base"] = "invalid_country_code" + elif not user_input[CONF_API_TOKEN]: + errors["base"] = "invalid_api_token" + else: + # Compute the updated CONF_FROM_NUMBER + full_number = f"{user_input[CONF_ORIG_COUNTRY_CODE].lstrip('+')}{user_input[CONF_ORIG_FROM_NUMBER]}" + user_input[CONF_FROM_NUMBER] = full_number + # Update entry with new options + return self.async_create_entry(title="", data=user_input) + + # Fetch current values from options + api_token = self.config_entry.options.get( + CONF_API_TOKEN, self.config_entry.data.get(CONF_API_TOKEN) + ) + from_number = self.config_entry.options.get( + CONF_ORIG_FROM_NUMBER, self.config_entry.data.get(CONF_ORIG_FROM_NUMBER, "") + ) + country_code = self.config_entry.options.get( + CONF_ORIG_COUNTRY_CODE, + self.config_entry.data.get(CONF_ORIG_COUNTRY_CODE, ""), + ) + + options_schema = vol.Schema( + { + vol.Required( + CONF_API_TOKEN, + default=self.config_entry.options.get( + CONF_API_TOKEN, self.config_entry.data.get(CONF_API_TOKEN) + ), + ): cv.string, + vol.Required( + CONF_ORIG_FROM_NUMBER, + default=self.config_entry.options.get( + CONF_ORIG_FROM_NUMBER, + self.config_entry.data.get(CONF_ORIG_FROM_NUMBER, ""), + ), + ): cv.string, + vol.Required( + CONF_ORIG_COUNTRY_CODE, + default=self.config_entry.options.get( + CONF_ORIG_COUNTRY_CODE, + self.config_entry.data.get(CONF_ORIG_COUNTRY_CODE, ""), + ), + ): cv.string, + } + ) + + return self.async_show_form( + step_id="init", data_schema=options_schema, errors=errors + ) \ No newline at end of file diff --git a/custom_components/whatspie/const.py b/custom_components/whatspie/const.py new file mode 100755 index 0000000..a2f60b3 --- /dev/null +++ b/custom_components/whatspie/const.py @@ -0,0 +1,8 @@ +"""Constants for the Whatspie integration.""" + +DOMAIN = "whatspie" +CONF_API_TOKEN = "api_token" +CONF_FROM_NUMBER = "from_number" +CONF_COUNTRY_CODE = "country_code" +CONF_ORIG_FROM_NUMBER = "orig_from_number" +CONF_ORIG_COUNTRY_CODE = "orig_country_code" \ No newline at end of file diff --git a/custom_components/whatspie/manifest.json b/custom_components/whatspie/manifest.json old mode 100644 new mode 100755 index 0e7d80a..7dea1be --- a/custom_components/whatspie/manifest.json +++ b/custom_components/whatspie/manifest.json @@ -1,10 +1,15 @@ { "domain": "whatspie", "name": "Send WhatsApp Messages via WhatsPie", + "config_flow": true, "codeowners": ["@arifwn"], - "dependencies": [], + "dependencies": ["notify"], + "integration_type": "service", "documentation": "https://github.com/arifwn/homeassistant-whatspie-integration", "iot_class": "cloud_push", "issue_tracker": "https://github.com/arifwn/homeassistant-whatspie-integration/issues", - "version": "1.0.3" + "version": "2.0.0", + "requirements": ["httpx>=0.24.0"], + "supported_features": ["notify"], + "domains": ["notify"] } diff --git a/custom_components/whatspie/notify.py b/custom_components/whatspie/notify.py old mode 100644 new mode 100755 index 101529b..33a1ef0 --- a/custom_components/whatspie/notify.py +++ b/custom_components/whatspie/notify.py @@ -1,61 +1,229 @@ """Send notifications as WhatsApp messages via WhatsPie""" +import httpx +import ssl +import certifi import logging import json import requests -_LOGGER = logging.getLogger(__name__) - +from homeassistant.const import CONF_API_TOKEN +from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.helpers.entity_platform import async_get_current_platform from homeassistant.components.notify import ( ATTR_DATA, ATTR_TARGET, PLATFORM_SCHEMA, BaseNotificationService, + NotifyEntity, ) +from .const import CONF_API_TOKEN, CONF_FROM_NUMBER, CONF_COUNTRY_CODE + +_LOGGER = logging.getLogger(__name__) WHATSPIE_API_ENDPOINT = "https://api.whatspie.com" -def sanitize_number(phone_number, country_code): +async def async_setup_entry(hass: HomeAssistant, entry, async_add_entities): + """Set up WhatsPie notify platform from a config entry.""" + config_data = entry.data + _LOGGER.debug("Config entry data received in notify platform: %s", config_data) + + if not all( + k in config_data for k in [CONF_API_TOKEN, CONF_FROM_NUMBER, CONF_COUNTRY_CODE] + ): + _LOGGER.debug( + "Missing required configuration data for WhatsPie notify platform." + ) + return + + entity = WhatsPieNotificationService( + hass=hass, + api_token=config_data[CONF_API_TOKEN], + from_number=config_data[CONF_FROM_NUMBER], + country_code=config_data[CONF_COUNTRY_CODE], + ) + + await async_register_notify_service(hass, entity) + async_add_entities([entity], update_before_add=False) + + +async def async_register_notify_service(hass: HomeAssistant, entity: NotifyEntity): + """Register the WhatsPie notify entity with the notify service.""" + platform = async_get_current_platform() + platform_name = platform.domain + + if platform_name != "notify": + _LOGGER.error("Notify entity must be registered under the notify domain.") + return + + notify_service_name = entity.name or "whatspie" + hass.services.async_register( + "notify", + notify_service_name, + entity.async_send_message, + ) + + _LOGGER.debug( + "WhatsPie notify service '%s' registered successfully.", notify_service_name + ) + + +def sanitize_number(phone_number): + """Sanitize a phone number and ensure proper formatting.""" + + # Check if phone_number already includes the full country code + if phone_number.startswith("+"): + return phone_number.lstrip("+") + + # Remove leading '0' or '+' from the phone number + if phone_number.startswith("0"): + phone_number = phone_number[1:] + elif phone_number.startswith("+"): + phone_number = phone_number[1:] + + return phone_number + + +async def async_send_whatsapp_text_message( + hass: HomeAssistant, to, message, api_token, from_number +) -> bool: + """Send a WhatsApp message via WhatsPie API.""" + + url = "https://api.whatspie.com/messages" + headers = { + "Authorization": f"Bearer {api_token}", + "Content-Type": "application/json", + } + payload = { + "receiver": sanitize_number(str(to)), + "device": from_number, + "message": message, + "type": "chat", + "simulate_typing": 1, + } + + _LOGGER.debug("Sending WhatsPie message with payload: %s", payload) + + try: + ssl_context = await hass.async_add_executor_job( + lambda: ssl.create_default_context(cafile=certifi.where()) + ) + + async with httpx.AsyncClient(verify=ssl_context) as client: + response = await client.post(url, json=payload, headers=headers) + + if 200 <= response.status_code < 300: + _LOGGER.debug("Message sent successfully to %s", to) + return True + + _LOGGER.error( + "WhatsPie API responded with error: %d - %s", + response.status_code, + response.text, + ) + return False + + except httpx.RequestError as err: + _LOGGER.error("Error connecting to WhatsPie API: %s", err) + + return False + + +class WhatsPieNotificationService(NotifyEntity): + """Implement the notification service for WhatsPie.""" + + def __init__(self, hass: HomeAssistant, api_token, from_number, country_code): + """Initialize the service.""" + self.hass = hass + self.api_token = api_token + self.from_number = from_number + self.country_code = country_code + _LOGGER.debug("WhatsPieNotificationService initialized: %s", self.unique_id) + + @property + def unique_id(self): + """Return a unique identifier for this notification service.""" + return f"whatspie_{self.from_number}" + + @property + def name(self): + """Return the name of the notification service.""" + return f"whatspie_{self.from_number}" + + async def async_send_message(self, service_call: ServiceCall) -> None: + """Send a WhatsApp message.""" + # Extract data from the service call + message = service_call.data.get("message") + target = service_call.data.get("target", []) + + _LOGGER.debug( + "async_send_message called with message: %s, target: %s", message, target + ) + + # Validate that `message` and `target` are present + if not message: + _LOGGER.warning("No message specified for WhatsPie notification.") + return + + if not target: + _LOGGER.warning("No targets specified for WhatsPie notification.") + return + + # Ensure target is always a list (even if a single string is provided) + if isinstance(target, str): + target = [target] + + # Iterate over each target and send the message + for to in target: + success = await async_send_whatsapp_text_message( + self.hass, to, message, self.api_token, self.from_number + ) + if not success: + _LOGGER.error("Failed to send message to recipient: %s", to) + + +def sanitize_legacy(phone_number, country_code): if len(phone_number) == 0: return phone_number - if phone_number[0] == '+': + if phone_number[0] == "+": return phone_number[1:] - if phone_number[0] == '0': + if phone_number[0] == "0": return country_code + phone_number[1:] return phone_number -def send_whatsapp_text_message(to, message, api_token, from_number, country_code): +def send_whatsapp_legacy_message(rec, message, api_token, from_number, country_code): if not WHATSPIE_API_ENDPOINT: return False - resp = requests.post(f'{WHATSPIE_API_ENDPOINT}/messages', - data=json.dumps({ - 'receiver': sanitize_number(str(to), country_code), - 'device': from_number, - 'message': message, - 'type': 'chat', - 'simulate_typing': 1, - }), - headers={ - 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36', - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'Authorization': f'Bearer {api_token}' - } - ) + resp = requests.post( + f"{WHATSPIE_API_ENDPOINT}/messages", + data=json.dumps( + { + "receiver": sanitize_legacy(str(rec), country_code), + "device": from_number, + "message": message, + "type": "chat", + "simulate_typing": 1, + } + ), + headers={ + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36", + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": f"Bearer {api_token}", + }, + ) if resp.status_code == 200: return True - _LOGGER.warning( - "WhatsPie HTTP API Response: %d - %s", resp.status_code, resp.text - ) + _LOGGER.warning("WhatsPie HTTP API Response: %d - %s", resp.status_code, resp.text) return False -class WhatsPieNotificationService(BaseNotificationService): +class WhatsPieLegacyNotificationService(BaseNotificationService): """Implement the notification service for the WhatsPie service.""" def __init__(self, api_token, from_number, country_code): @@ -70,17 +238,21 @@ def send_message(self, message="", **kwargs): data = kwargs.get(ATTR_DATA) or {} file_url = None - if 'media_url' in data: - file_url = data['media_url'] + if "media_url" in data: + file_url = data["media_url"] if not targets: _LOGGER.info("At least 1 target is required") return for target in targets: - send_whatsapp_text_message(target, message, self.api_token, self.from_number, self.country_code) + send_whatsapp_legacy_message( + target, message, self.api_token, self.from_number, self.country_code + ) def get_service(hass, config, discovery_info=None): """Get the WhatsPie notification service.""" - return WhatsPieNotificationService(config['api_token'], config['from_number'], config['country_code']) + return WhatsPieLegacyNotificationService( + config["api_token"], config["from_number"], config["country_code"] + ) diff --git a/custom_components/whatspie/services.yaml b/custom_components/whatspie/services.yaml new file mode 100755 index 0000000..63a3734 --- /dev/null +++ b/custom_components/whatspie/services.yaml @@ -0,0 +1,10 @@ +notify: + name: Send WhatsApp Message via WhatsPie + description: Send a WhatsApp notification via WhatsPie. + fields: + data: + description: Data containing the message and target for the notification. + example: + message: "Test Notification" + target: + - "+123456789" \ No newline at end of file diff --git a/custom_components/whatspie/translations/en.json b/custom_components/whatspie/translations/en.json new file mode 100755 index 0000000..5fc3c86 --- /dev/null +++ b/custom_components/whatspie/translations/en.json @@ -0,0 +1,49 @@ +{ + "title": "WhatsPie", + "config": { + "step": { + "user": { + "title": "WhatsPie Configuration", + "description": "Set up WhatsPie to send WhatsApp notifications.", + "data": { + "api_token": "API Token", + "from_number": "WhatsApp Phone Number", + "country_code": "Country Code" + } + } + }, + "error": { + "auth_error": "Invalid API Token or Phone Number", + "connection_error": "Cannot connect to WhatsPie API. Check your network or API URL.", + "unknown_error": "An unexpected error occurred. Please try again.", + "invalid_phone_number": "The phone number is invalid. Please check the format.", + "invalid_country_code": "The country code is invalid. Please provide a valid code.", + "invalid_api_token": "The API token is invalid or missing. Please provide a valid API token." + } + }, + "services": { + "notify": { + "fields": { + "data": { + "description": "Data for the WhatsPie notification, including message and target.", + "example": { + "message": "Test Notification", + "target": ["+123456789"] + } + } + } + } + }, + "options": { + "step": { + "init": { + "title": "Edit WhatsPie Options", + "description": "Update WhatsPie configuration.", + "data": { + "from_number": "WhatsPie Phone Number", + "country_code": "Country Code" + } + } + } + } +}