Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 59 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# green-api-custom-notifier

[green-api](https://green-api.com/en) is a service that allows us to send and receive text, photo and video using stable WhatsApp API gateway. The service includes free account that can be used to send notifications to 3 chats (Group or Private) and many more.
[green-api](https://green-api.com/en) is a service that allows us to send and receive text, photo and video using stable WhatsApp API gateway. The service includes a free account that can be used to send notifications to 3 chats (Group or Private) and many more.


[green-api-custom-notifier](https://github.com/t0mer/green-api-custom-notifier) is a [Homeassistant ](https://www.home-assistant.io/) custom notification component that enables us to send notification to Whatsapp groups using [green-api](https://green-api.com/en).
[green-api-custom-notifier](https://github.com/t0mer/green-api-custom-notifier) is a [Home Assistant](https://www.home-assistant.io/) custom notification component that enables sending notifications to WhatsApp groups and contacts using [green-api](https://green-api.com/en).


## Limitations
Expand All @@ -13,7 +12,7 @@
## Getting started

### Setup Green API account
Nevigate to [https://green-api.com/en](https://green-api.com/en) and register for a new account:
Navigate to [https://green-api.com/en](https://green-api.com/en) and register for a new account:
![Register](screenshots/register.png)

Fill up your details and click on **Register**:
Expand All @@ -28,92 +27,100 @@ Select the "Developer" instance (Free):
![Developer Instance](screenshots/developer_instance.png)


Copy the InstanceId and Token, we need it for the integration settings:
Copy the InstanceId and Token — you will need these during integration setup:
![Instance Details](screenshots/instance_details.png)

Next, Lets connect our whatsapp with green-api. On the left side, Under API --> Account, click on QR and copy the QR URL to the browser and click on "Scan QR code"
Next, connect your WhatsApp with Green API. On the left side, under API Account, click on QR and copy the QR URL to the browser and click on "Scan QR code":

![Send QR](screenshots/send_qr.png)

![Scan QR](screenshots/scan_qr.png)

Next, Scan the QR code to link you whatsapp with Green API:
Scan the QR code to link your WhatsApp with Green API:

![QR Code](screenshots/qr.png)

After the account link, you will notice that the instance is active by the green light in the instance header:
After linking, the instance will show as active with a green light in the instance header:
![Active Instance](screenshots/active_instance.png)



### Getting the Contacts and Groups
Before we can start messaging, we need to get the Contact/Group details. we can do it using Green API endpoint.
On the lef side, Under API --> Service methods, click on "getContacts" and then click "Send":
### Getting Contacts and Groups
Before messaging, get the Contact/Group IDs via the Green API endpoint.
On the left side, under API → Service methods, click on "getContacts" and then click "Send":
![Get Contacts](screenshots/get_contacts.png)

As a result, you will get the list of Contacts and Groups.
* The contact number ends with **@c.us**
* The group number ends with **@g.us**
You will get a list of contacts and groups:
* Contact numbers end with **@c.us**
* Group numbers end with **@g.us**

![Contacts Lists](screenshots/contacts_list.png)

Write down the Id, you will need it to configure the notification.
Note the ID — you will need it when sending notifications.


### Setting up the notification in Home Assistant
### Installing the integration

Download the [green-api-custom-notifier](https://github.com/t0mer/green-api-custom-notifier), place it under the **custom_components** folder.
Restart Home Assistant and add the following section to your *configuration.yaml* file:
Download the [green-api-custom-notifier](https://github.com/t0mer/green-api-custom-notifier) and place the `greenapi` folder under your `custom_components` directory. Restart Home Assistant.

### Configuring via UI

```yaml
notify:
- platform: greenapi
name: greenapi
instance_id: #REQUIRED: Set the instanceid
token: #REQUIRED: Set the greenapi token.
target: #OPTIONAL! Set the detault target. If you set the default target here, you won't have to specify it again in your service calls.
```
Go to **Settings → Integrations → Add Integration** and search for **GreenAPI**. Fill in the three fields:

* instance_id is the Green API instance id.
* token is the Green API instance token.
* target is the chat/contact/group id to send the message to:
* For groups, the id should end with *@g.us*
* For chats, the id should end with *@c.us*
| Field | Description |
|-------|-------------|
| **Service Name** | A short friendly name (e.g. `home`). The notify service will be registered as `notify.greenapi_<name>`. |
| **Instance ID** | Your Green API Instance ID. |
| **Access Token** | Your Green API token. |

You can add multiple instances (e.g. for different WhatsApp accounts) — each gets its own uniquely named service.


## Sending a message
To Send a message you call the service and provide the following parameters:
* message (**Required**): Test to send.
* title (**OPTIONAL**): Add a title for the message in **bold**.
* target (**OPTIONAL** if you've already defined the default target in your notify service, otherwise required): The chat/group id to send the message to.

![Send text message](screenshots/text_message.png)
Call the service with the following parameters:

| Parameter | Required | Description |
|-----------|----------|-------------|
| `message` | Yes | Text to send. |
| `title` | No | Prepended to the message in **bold**. |
| `target` | Yes | The WhatsApp chat/group ID to send to. |

```yaml
action: notify.greenapi_home
data:
message: Hello from Home Assistant
target: 972XXXXXXXXX@c.us
```

With a title:
```yaml
action: notify.greenapi_home
data:
message: Motion detected in the garden
title: Security Alert
target: 972XXXXXXXXX@c.us
```

Or from Yaml mode:
For groups, the target ID ends with `@g.us`:
```yaml
service: notify.greenapi
action: notify.greenapi_home
data:
message: New Whatsapp component
target: 972*********@c.us
message: Dinner is ready!
target: 120363XXXXXXXXX@g.us
```

### Optional - Attach media to message
To send message with media, add the following to the data parameter:
* file : [Path to the file]

![Send media](screenshots/send_media.png)
### Optional — Attach media to message

Add a `file` key inside the `data` field to attach a local file:

Or from Yaml mode:
```yaml
service: notify.greenapi
action: notify.greenapi_home
data:
message: New Whatsapp component
target: 972*********@c.us
message: Here is your image
target: 972XXXXXXXXX@c.us
data:
file: /config/images/Capture.png

```

#### Important
If the path to the file does not exist, the message will still be sent; but will log a warning.
> **Note:** If the file path does not exist, the message is still sent as text and a warning is logged.
136 changes: 135 additions & 1 deletion custom_components/greenapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,135 @@

"""GreenAPI WhatsApp notifier — setup and teardown."""

from __future__ import annotations

import functools
import logging
import os
import re
from dataclasses import dataclass
from os.path import basename
from urllib.parse import urlparse

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, ServiceCall

from .const import CONF_INSTANCE_ID, CONF_NAME, CONF_TOKEN, DOMAIN

_LOGGER = logging.getLogger(__name__)

_NOTIFY_DOMAIN = "notify"

_SERVICE_SCHEMA = vol.Schema(
{
vol.Required("message"): cv.string,
vol.Optional("title"): cv.string,
vol.Optional("target"): cv.string,
vol.Optional("data"): dict,
}
)


def _slugify(text: str) -> str:
return re.sub(r"[^a-z0-9]+", "_", text.lower()).strip("_")


@dataclass
class GreenAPIData:
"""Runtime data stored on the config entry."""

instance_id: str
token: str
service_name: str


type GreenAPIConfigEntry = ConfigEntry[GreenAPIData]


async def async_setup_entry(hass: HomeAssistant, entry: GreenAPIConfigEntry) -> bool:

Check warning on line 51 in custom_components/greenapi/__init__.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use asynchronous features in this function or remove the `async` keyword.

See more on https://sonarcloud.io/project/issues?id=t0mer_green-api-custom-notifier&issues=AZ0lO3WSjBAlinlhaMEO&open=AZ0lO3WSjBAlinlhaMEO&pullRequest=23

Check failure on line 51 in custom_components/greenapi/__init__.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 22 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=t0mer_green-api-custom-notifier&issues=AZ0lO3WSjBAlinlhaMEP&open=AZ0lO3WSjBAlinlhaMEP&pullRequest=23
"""Set up GreenAPI from a config entry."""
from whatsapp_api_client_python import API # noqa: PLC0415

data = {**entry.data, **entry.options}

instance_id: str = data[CONF_INSTANCE_ID]
token: str = data[CONF_TOKEN]
name: str = data.get(CONF_NAME) or instance_id
service_name: str = f"greenapi_{_slugify(name)}"

entry.runtime_data = GreenAPIData(
instance_id=instance_id,
token=token,
service_name=service_name,
)

greenapi = API.GreenAPI(instance_id, token)

async def handle_send_message(call: ServiceCall) -> None:
message: str = call.data["message"]
title: str | None = call.data.get("title")
extra_data: dict | None = call.data.get("data")

if title:
message = f"*{title}*\n{message}"

dest: str | None = call.data.get("target")
if not dest:
_LOGGER.error("No target specified — pass 'target' in the service call data")
return

_LOGGER.info("Sending message to %s", dest)

try:
if extra_data:
file_path = extra_data.get("file")
if file_path:
if os.path.exists(file_path):
upload_response = await hass.async_add_executor_job(
greenapi.sending.uploadFile, file_path
)
if upload_response.code != 200:
raise Exception(
upload_response.code,
f"Failed to upload file: {file_path}",
)

Check warning on line 97 in custom_components/greenapi/__init__.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this generic exception class with a more specific one.

See more on https://sonarcloud.io/project/issues?id=t0mer_green-api-custom-notifier&issues=AZ0lO3WSjBAlinlhaMEQ&open=AZ0lO3WSjBAlinlhaMEQ&pullRequest=23
url_file = upload_response.data["urlFile"]
file_name = basename(urlparse(url_file).path)
await hass.async_add_executor_job(
functools.partial(
greenapi.sending.sendFileByUrl,
dest,
url_file,
file_name,
caption=message,
)
)
return
else:
_LOGGER.warning(
"Sending message to %s: file '%s' not found, sending text only",
dest,
file_path,
)

await hass.async_add_executor_job(
functools.partial(
greenapi.sending.sendMessage, dest, message, linkPreview=False
)
)
except Exception as e:
_LOGGER.error("Failed to send message to %s: %s", dest, e)

hass.services.async_register(
_NOTIFY_DOMAIN, service_name, handle_send_message, schema=_SERVICE_SCHEMA
)

return True


async def async_unload_entry(hass: HomeAssistant, entry: GreenAPIConfigEntry) -> bool:

Check warning on line 132 in custom_components/greenapi/__init__.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use asynchronous features in this function or remove the `async` keyword.

See more on https://sonarcloud.io/project/issues?id=t0mer_green-api-custom-notifier&issues=AZ0lO3WSjBAlinlhaMER&open=AZ0lO3WSjBAlinlhaMER&pullRequest=23
"""Unload a config entry."""
hass.services.async_remove(_NOTIFY_DOMAIN, entry.runtime_data.service_name)
return True
41 changes: 41 additions & 0 deletions custom_components/greenapi/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Config flow for GreenAPI WhatsApp notifier."""

from __future__ import annotations

from typing import Any

import voluptuous as vol
from homeassistant import config_entries

from .const import CONF_INSTANCE_ID, CONF_NAME, CONF_TOKEN, DOMAIN

_SETUP_SCHEMA = vol.Schema(
{
vol.Required(CONF_NAME): str,
vol.Required(CONF_INSTANCE_ID): str,
vol.Required(CONF_TOKEN): str,
}
)


class GreenAPIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle the initial setup wizard."""

VERSION = 1

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> config_entries.ConfigFlowResult:
if user_input is not None:
await self.async_set_unique_id(user_input[CONF_INSTANCE_ID])
self._abort_if_unique_id_configured()

return self.async_create_entry(
title=user_input[CONF_NAME],
data=user_input,
)

return self.async_show_form(
step_id="user",
data_schema=_SETUP_SCHEMA,
)
4 changes: 4 additions & 0 deletions custom_components/greenapi/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
DOMAIN = "greenapi"
CONF_INSTANCE_ID = "instance_id"
CONF_TOKEN = "token"
CONF_NAME = "name"
6 changes: 4 additions & 2 deletions custom_components/greenapi/manifest.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"domain": "greenapi",
"name": "Whatsapp notifier basde on Green API",
"name": "Whatsapp notifier based on Green API",
"codeowners": [
"@t0mer"
],
"documentation": "https://github.com/t0mer/green-api-custom-notifier",
"iot_class": "local_polling",
"issue_tracker": "https://github.com/t0mer/green-api-custom-notifier",
"requirements": ["whatsapp-api-client-python"],
"version": "0.4.0"
"version": "0.5.0",
"integration_type": "service",
"config_flow": true
}
Loading