Compare commits

..
40 Commits
Author SHA1 Message Date
allburov 9e6cd39b2d [core] fix types
Release / amd64 - none - noweb (push) Waiting to run
Release / linux/arm64 - none - noweb-arm (push) Waiting to run
Release / amd64 - chrome - chrome (push) Waiting to run
Release / amd64 - chromium - latest (push) Waiting to run
Release / linux/arm64 - chromium - arm (push) Waiting to run
2024-02-01 16:23:42 +03:00
allburov 8898429046 [core] Update engines 2024-02-01 16:23:36 +03:00
allburov a74def6dbd [core] Add Patron Portal link 2024-02-01 16:15:42 +03:00
allburov 9f783cd35e [core] Add Patron Portal link 2024-02-01 16:14:04 +03:00
allburov 9d2f061d81 [core] Pass other parameters to requestCode, like locale 2024-01-27 15:11:16 +07:00
allburov a6045b2d54 [core] Add badge example 2024-01-27 13:58:50 +07:00
allburov 6142b2b564 [core] Link fixes 2024-01-27 13:56:30 +07:00
allburov ed53ccb919 [core] One more PRO tier perk 2024-01-27 13:54:38 +07:00
allburov 1ab45c6cfd [core] Add MOBILE engine note 2024-01-27 13:40:10 +07:00
allburov c2fdb090e1 [core] build via ssh 2024-01-27 13:40:10 +07:00
allburov a10b47eb7e [core] Dockerfile - mount ssh in case of private repositories 2024-01-27 13:40:10 +07:00
allburov 7b234e2e37 [core] make build-ssh 2024-01-27 13:40:10 +07:00
allburov 90d45a4698 [core] Add captcha API endpoints 2024-01-27 13:40:10 +07:00
allburov 5e40b70e19 [core] NOWEB - Listen connection events from the start 2024-01-27 13:40:10 +07:00
allburov a27d0c3efb [core] Add MOBILE engine 2024-01-27 13:40:10 +07:00
allburov 46ed506b35 [core] start stop typing in the example 2024-01-27 13:01:52 +07:00
allburov 0ad813a1fe [core] Add links 2024-01-08 18:58:44 +07:00
allburov e57e42f47a [core] Add tiers 2024-01-08 18:53:31 +07:00
Alex cc58157d37 [core] Add badges for patreons 2024-01-08 11:55:34 +07:00
Alex 9b5bb0a339 [core] Create gh-team-labels.yml 2024-01-07 16:34:03 +07:00
Alex 84ebbea287 Delete .github/.github/gh-team-labels.yml 2024-01-07 16:33:44 +07:00
Alex 1c2021f54b Update gh-team-labels.yml 2024-01-07 16:33:09 +07:00
Alex 98a7f00d7d [core] Update gh-team-labels.yml 2024-01-07 16:29:14 +07:00
Alex 60d97d0e67 [core] Create gh-team-labels.yml 2024-01-07 16:28:17 +07:00
allburov 03f4a07cbe [core] Update NOWEB 2023-12-26 18:33:53 +07:00
allburov 3fbe526793 [core] log phone 2023-12-26 17:54:34 +07:00
allburov a892f03382 [core] Bump version to 2023.12.3 2023-12-26 17:20:12 +07:00
allburov 8db69f6e69 [core] Update WEBJS 2023-12-26 17:20:00 +07:00
allburov f9037ff9fc [core] Add github link to authroize-code 2023-12-26 17:10:37 +07:00
allburov 366ddffa2b [core] Add more about How to not get Banned 2023-12-22 16:05:25 +07:00
allburov b8468588de [core] Add note about wa.me link 2023-12-18 21:12:43 +07:00
allburov 62dd6f7f4a [core] No --rm 2023-12-11 13:26:33 +07:00
allburov 858202f1bb [core] Add chatId to changelog 2023-12-02 16:36:20 +07:00
allburov db7900dc26 [core] Bump version to 2023.12.2 2023-12-02 16:34:20 +07:00
allburov aa87e437dc [core] Add note for Brazilian phone number 2023-12-02 16:33:50 +07:00
allburov 741e5e86f8 [core] Reformat send messages documentation 2023-12-02 16:28:44 +07:00
allburov 390a171891 [core] Document check exists changes 2023-12-02 16:23:00 +07:00
allburov 1fc5e2b47c [core] Add chatId to check exists result 2023-12-02 16:13:23 +07:00
allburov 8926dc7cc6 [core] update swagger 2023-12-01 09:38:52 +07:00
allburov 5c3d3c0d70 [core] No null in swagger 2023-12-01 09:38:52 +07:00
24 changed files with 320 additions and 107 deletions

No files matched your search

+15
View File
@@ -0,0 +1,15 @@
ignore:
teams:
- no-labels
users:
- allburov
teamLabels:
- team: waha-patrons-pro
label: patron:PRO
comment: '[![patron:PRO](https://img.shields.io/badge/patron-PRO-188a42)](https://waha.devlike.pro/docs/how-to/plus-version/#tiers)'
- team: waha-patrons-advanced
label: patron:ADVANCED
comment: '[![patron:ADVANCED](https://img.shields.io/badge/patron-ADVANCED-4cd07d)](https://waha.devlike.pro/docs/how-to/plus-version/#tiers)'
- team: waha-patrons-plus
label: patron:PLUS
comment: '[![patron:PLUS](https://img.shields.io/badge/patron-PLUS-a0e6ba)](https://waha.devlike.pro/docs/how-to/plus-version/#tiers)'
+7 -2
View File
@@ -5,16 +5,21 @@ FROM node:20-bullseye as build
ENV PUPPETEER_SKIP_DOWNLOAD=True
# npm packages
RUN apt-get update \
&& apt-get install -y openssh-client
RUN apt install openssh-client
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
WORKDIR /src
COPY package.json .
COPY yarn.lock .
RUN yarn set version 3.6.3
RUN yarn install
RUN --mount=type=ssh yarn install
# App
WORKDIR /src
ADD . /src
RUN yarn install
RUN --mount=type=ssh yarn install
RUN yarn build && find ./dist -name "*.d.ts" -delete
#
+7
View File
@@ -12,6 +12,13 @@ build-all: build build-chrome build-noweb
build-plus:
docker build . -t devlikeapro/whatsapp-http-api-plus
build-ssh:
# check IMAGE provided
@[ "${IMAGE}" ] || ( echo "Add APP: make build-ssh image=devlikeapro/whatsapp-http-api"; exit 1 );
eval $(ssh-agent) && \
ssh-add ~/.ssh/id_rsa && \
docker buildx build --ssh default=${SSH_AUTH_SOCK} . -t ${IMAGE} --build-arg USE_BROWSER=none
stop:
docker stop whatsapp-http-api
@@ -77,6 +77,12 @@
url = "/docs/how-to/plus-version"
parent = "waha-plus"
[[main]]
name = "Patron Portal ->"
weight = 20
url = "https://portal.devlike.pro/"
parent = "waha-plus"
[[main]]
name = "Get on Patreon ->"
weight = 50
@@ -63,13 +63,26 @@ Get contact
If you want to check if phone number is registered in WhatsApp (even if the number is not in your contact list) - use
this endpoint for that.
- `GET /api/contacts/check-exists?phone=11231231231&session=default`
```bash
GET /api/contacts/check-exists?phone=11231231231&session=default
```
It returns `numberExists` field with `true` or `false` value and `chatId` field with chat ID of the number (if exists).
```json
{
"numberExists": true
"numberExists": true,
"chatId": "123123123@c.us"
}
```
**Note for Brazilian Phone Numbers**
You should use the `GET /api/contacts/check-exists` endpoint **before sending a message to a new phone number**
to get the correct chatId because of the additional 9-digit number added after 2012.
Read more about
[error sending text to half of Brazilian numbers (every number registered before 2012) ->](https://github.com/devlikeapro/whatsapp-http-api/issues/238)
It's fine to send the response to `chatId` for incoming messages, though - the payload already has the correct `chatId`.
### Get "about" contact
@@ -49,6 +49,16 @@ Not running Chromium saves you CPU and Memory, so you can run more instances on
⚠ Read the article before using **NOWEB** engine
[How to avoid blocking ->]({{< relref " /docs/overview/how-to-avoid-blocking" >}}).
### MOBILE (experimental)
{{< alert icon="👉" text="<b>Experimental engine!</b><br/>Please contact us on <a href='https://www.patreon.com/wa_http_api' target='_blank'>Patreon -></a> or <a href='https://boosty.to/wa-http-api' target='_blank'>Boosty -></a> if you're interested in using it.<br>Available for <a href='https://waha.devlike.pro/docs/how-to/plus-version/#tiers' target='_blank'>PRO</a> subscribers only" />}}
- `WHATSAPP_DEFAULT_ENGINE=MOBILE`
- [https://github.com/WhiskeySockets/Baileys](https://github.com/WhiskeySockets/Baileys)
**MOBILE** engine **does not require a browser and a mobile device** to work with WhatsApp, it does so directly using a WebSocket.
You don't have to use a mobile device to run this engine.
### VENOM
- `WHATSAPP_DEFAULT_ENGINE=VENOM`
@@ -98,20 +108,22 @@ it's the engine WAHA runs by default.
If you find any inconsistency with actual endpoints -
please [create an issue](https://github.com/devlikeapro/whatsapp-http-api/issues/new?title=Error+in+engine+features )
| | WEBJS | NOWEB | VENOM |
|-------------------------------------------|:-----:|:-----:|:-----:|
| **Session** | | | |
| `POST /api/sessions/start` | ✔️ | ✔️ | ✔️ |
| `POST /api/sessions/stop` | ✔️ | ✔️ | ✔️ |
| `POST /api/sessions/logout` | ✔️ | ✔️ | ✔️ |
| `GET /api/sessions/` | ✔️ | ✔️ | ✔️ |
| `GET /api/sessions/{session}/me` | ✔️ | ✔️ | ➖ |
| **Authentication** | | | |
| `POST /api/{session}/auth/qr` | ✔️ | ✔️ | ✔️ |
| `POST /api/{session}/auth/request-code` | ➖ | ✔️ | ➖ |
| `POST /api/{session}/auth/authorize-code` | ➖ | ✔️ | ➖ |
| **Screenshot** | | | |
| `POST /api/screenshot` | ✔️ | ➖ | ✔️ |
| | WEBJS | NOWEB | VENOM |
|-------------------------------------------------------------------|:-----:|:-------------------------------------------------------------------------:|:-----:|
| **Session** | | | |
| `POST /api/sessions/start` | ✔️ | ✔️ | ✔️ |
| `POST /api/sessions/stop` | ✔️ | ✔️ | ✔️ |
| `POST /api/sessions/logout` | ✔️ | ✔️ | ✔️ |
| `GET /api/sessions/` | ✔️ | ✔️ | ✔️ |
| `GET /api/sessions/{session}/me` | ✔️ | ✔️ | ➖ |
| **Authentication** | | | |
| `POST /api/{session}/auth/qr` | ✔️ | ✔️ | ✔️ |
| `POST /api/{session}/auth/request-code` | ➖ | ✔️ | ➖ |
| `POST /api/{session}/auth/authorize-code` | ➖ | ➖️<br>[#113](https://github.com/devlikeapro/whatsapp-http-api/issues/113) | ➖ |
| `GET /api/{session}/auth/captcha` <br>Only in **MOBILE** engine | ➖ | ➖ | ➖ |
| `POST /api/{session}/auth/captcha` <br>Only in **MOBILE** engine | ➖ | ➖ | ➖ |
| **Screenshot** | | | |
| `POST /api/screenshot` | ✔️ | ➖ | ✔️ |
| **Chatting** | WEBJS | NOWEB | VENOM |
|------------------------------------------------------|:-----:|:-----:|:-----:|
@@ -79,15 +79,18 @@ What is the difference between **WAHA Plus** and **other SaaS solutions** for Wh
The more you support the project - the more you get!
### Tiers
Here's available tiers for our supporters:
| | Plus | Advanced | Pro |
|-------------------------------------------------------------------------------------|---------|:--------:|:-------:|
| Access to WAHA Plus ![](/images/versions/plus.png) docker image. | ✔️ | ✔️ | ✔️ |
| An invitation to a private Github repository so you have the WAHA Plus source code. | ➖️ | ✔️ | ✔️️ |
| Access to a Telegram group where we provide support and assistance. | ➖️ | ➖ | ✔️ |
| Personal key to Docker Hub. | ➖️ | ➖ | ✔️ |
| **Donation** | **$19** | **$39** | **$99** |
| | Plus | Advanced | Pro |
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|:--------:|:-------:|
| Access to WAHA Plus ![](/images/versions/plus.png) docker image with a personal key. | ✔️ | ✔️ | ✔️ |
| Special badge ([![patron:TIER](https://img.shields.io/badge/patron-TIER-188a42)](https://waha.devlike.pro/docs/how-to/plus-version/#tiers)) and labels (`patron:TIER`) on [project's issues, PRs and Discussion](https://github.com/devlikeapro/whatsapp-http-api) to highlight your support to the project! | ✔️ | ✔️ | ✔️ |
| An invitation to a private Github repository so you have the WAHA Plus source code. | ➖️ | ✔️ | ✔️️ |
| Access to a Telegram group where we provide support and assistance. | ➖️ | ➖ | ✔️ |
| Access to an [experimental MOBILE engine]({{< relref "/docs/how-to/plus-version#tiers" >}})!. | ➖️ | ➖ | ✔️ |
| **Donation** | **$19** | **$39** | **$99** |
For **$19** donation you get:
1. **Secret password** for Docker Hub to download Plus image: `devlikeapro/whatsapp-http-api-plus`.
@@ -127,8 +130,9 @@ Boosty ->
Go ahead and get your own WhatsApp HTTP API!
### Install Plus
After you get the password, use it to login and download docker image:
## Install Plus
After you get the password, get your login to Docker Hub in [Patron Portal ->](https://portal.devlike.pro/)
and run the commands:
```bash
docker login -u devlikeapro -p {KEY}
docker pull devlikeapro/whatsapp-http-api-plus
@@ -15,18 +15,34 @@ If you haven't yet - please follow the steps from [**Quick Start →**]({{< relr
## Fields
There are common fields that you can find in almost all requests:
- `session` - a session name from which account you're sending the message. We use `default` in the examples.
- Core ![](/images/versions/core.png) version supports only `default` session.
- Plus ![](/images/versions/plus.png) allows you to run multiple sessions inside one container to save your memory and CPU resources!
```json
{
"session": "default",
"chatId": "12132132130@c.us",
"file": "..."
}
```
### session
`session` - a session name from which account you're sending the message. We use `default` in the examples.
- Core ![](/images/versions/core.png) version supports only `default` session.
- Plus ![](/images/versions/plus.png) allows you to run multiple sessions inside one container to save your memory and CPU resources!
Read more about [multiple sessions →]({{< relref "/docs/how-to/sessions" >}})
- `chatId` - it's a phone number or Group identifier where you're sending the message.
- **Phone numbers** accounts use international phone number without + at the start and add `@c.us` at the end.
For phone number `12132132131` the `chatId` is `12132132131@c.us`.
- **Groups** use random number with `@g.us` at the end.
### chatId
`chatId` - it's a phone number or Group identifier where you're sending the message.
- **Phone numbers** accounts use international phone number without + at the start and add `@c.us` at the end.
For phone number `12132132131` the `chatId` is `12132132131@c.us`.
- **Groups** use random number with `@g.us` at the end.
> To get the actual `chatId` for **Brazilian phone number** - use `chatId` field from
> [Check phone number exists ->]({{< relref "/docs/how-to/contacts#check-phone-number-exists" >}})
> response.
### file
When sending media (images, voice, files) you can either use:
- `file.data` field with base64 encoded file (https://base64.guru/converter/encode/file)
- `file.data` field [with base64 encoded file](https://base64.guru/converter/encode/file)
- `file.url` field with public available URL for that file
## Endpoints
@@ -311,7 +311,7 @@ to the container. WAHA stores authentication information in the directory and re
The full command would be:
```bash
docker run --rm -d -v `pwd`/.sessions:/app/.sessions -p 3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api-plus
docker run -d -v `pwd`/.sessions:/app/.sessions -p 3000:3000/tcp --name whatsapp-http-api devlikeapro/whatsapp-http-api-plus
```
#### Remote storage ![](/images/versions/soon.png)
@@ -19,6 +19,12 @@ If you wish to get and read Release Notes - read them on one of our platforms, *
You even can **subscribe to get new updates** there!
## 2023.12
December 2023
- Add `chatId` field to `GET /api/contacts/check-exists` to help get the right `chatId` for Brazilian numbers.
Read more about
[error sending text to half of Brazilian numbers (every number registered before 2012) ->](https://github.com/devlikeapro/whatsapp-http-api/issues/238)
## 2023.11
November 2023
- Add different formats for QR code:
@@ -27,6 +27,9 @@ When developing a bot for WhatsApp, you **should never initiate a conversation**
Instead, your bot **should only reply** to messages that it receives.
This will prevent your bot from being flagged as spam by WhatsApp's users and algorithms.
> You can use a short link http://wa.me/7911111111?text=Hi
> so a user can click on it and start the dialog with the bot first
### 2. Avoid Spamming and Sending Unnecessary Content
Sending too many messages or sending content that the user did not request can also lead to your bot being blocked. Make
@@ -56,3 +59,34 @@ Here's a recommended process to follow:
By following these steps, you can ensure that your bot processes messages in a way that's compliant with WhatsApp's
guidelines and reduces the risk of being blocked.
## How to not get Banned?
WhatsApp knows that it's uncommon for someone to send so many messages or bulk messages to people they've never talked to each other before so it is considered spam/marketing junk pretty fast. There are some tips before sending a message to WhatsApp
**Dos and Don'ts:**
1. Important: Do NOT send messages which get you a report. As long as you don't get reports from users who you sent a message, your account will be mostly fine.
2. Having real content, a survey that the person agreed with is different from a marketing message on a Saturday night.
3. Send messages written in different ways, you could make a script that places spacebars randomly on your string AND includes the person's (first) name
4. Never use fixed times, always go for sending the first message, wait a random time between 30 ~ and 60 seconds and then send the second message
5. Always try to group contacts by their area code, WhatsApp expects a regular person to talk mostly with contacts that are within the same area of your phone number.
6. Have a profile picture, this is not related to WhatsApp Bots Catcher® but sending a new message for someone not having a picture/name/status will elevate your chances of being manually tagged as spam.
7. Send "seen" confirmation to messages or disable it on WhatsApp
8. Avoid sending links that were previously marked as spam on WhatsApp or non-HTTPS. A URL shortener is a good idea
9. IMPORTANT: It's terrible if you send messages 24/7 without giving it some time to wait. Random delays between messages are not enough, send a reasonable amount of messages keeping in mind your conversion rate, For example: for one hour send a maximum of 4 messages per contact that have replied to your message, and stop sending messages for one hour, then start again. Again, don't send messages without stopping for a while between every "package"
10. Send only one short message when starting a conversation, one should not send long texts or multiple messages without the user consenting
**Have in mind:**
1. For every one you send a message that doesn't have your number on their contact list asked if it's spam, being tagged as spam a few times (5 - 10) will get you banned
2. WhatsApp records every movement you do, you can even check the logs when sending simple support email, it contains all kinds of information, this said: act as human as possible
3. Try to engage in conversations, as long as you send a message and the person doesn't automatically block you it'll be quite okay. People constantly talking to you and adding you to their contact list will make your number stronger against a ban
4. Think about that like a points system, you start with zero points (negative if your device was previously blacklisted), if you reach below zero you are out, if you engage in conversations you get a point, if you are tagged as spam you lose some points, if you are blocked you may lose more points
5. Finally, If your content is spam, it doesn't matter if you are using a broadcast list, group, or direct contact, you will still be banned
As API, we say all that's left to do right now is to agree with WhatsApp's policy, not send spam messages, and always wait for the other person to contact you first.
You could make it by sending an SMS to the person with a link to start a chat on WhatsApp with you by link [https://wa.me/12132132131?text=Hi](https://wa.me/12132132131?text=Hi).
+1 -1
View File
@@ -1 +1 @@
{"openapi":"3.1.0","paths":{"/api/{session}/auth/qr":{"get":{"operationId":"AuthController_getQR","summary":"Get QR code for pairing WhatsApp Web.","parameters":[{"name":"session","required":true,"in":"path","schema":{"default":"default"},"description":"WhatsApp session name"},{"name":"format","required":true,"in":"query","schema":{"default":"image","enum":["image","raw"],"type":"string"}}],"responses":{"200":{"content":{"image/png":{"schema":{"type":"string","format":"binary"}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Base64File"},{"$ref":"#/components/schemas/QRCodeValue"}]}}},"description":""}},"tags":["auth"],"security":[{"api_key":[]}]}},"/api/{session}/auth/request-code":{"post":{"operationId":"AuthController_requestCode","summary":"Request authentication code.","parameters":[{"name":"session","required":true,"in":"path","schema":{"default":"default"},"description":"WhatsApp session name"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestCodeRequest"}}}},"responses":{"201":{"description":""}},"tags":["auth"],"security":[{"api_key":[]}]}},"/api/{session}/auth/authorize-code":{"post":{"operationId":"AuthController_authorizeCode","summary":"Send OTP authentication code.","parameters":[{"name":"session","required":true,"in":"path","schema":{"default":"default"},"description":"WhatsApp session name"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OTPRequest"}}}},"responses":{"201":{"description":""}},"tags":["auth"],"security":[{"api_key":[]}]}},"/api/sessions/start":{"post":{"operationId":"SessionsController_start","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStartRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionDTO"}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/stop":{"post":{"operationId":"SessionsController_stop","summary":"Stop session","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStopRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/logout":{"post":{"operationId":"SessionsController_clean","summary":"Logout from session.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionLogoutRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions":{"get":{"operationId":"SessionsController_list","parameters":[{"name":"all","required":false,"in":"query","example":false,"description":"Return all sessions, including those that are in the STOPPED state.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SessionInfo"}}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/{session}/me":{"get":{"operationId":"SessionController_getMe","summary":"Get information about the authenticated account","parameters":[{"name":"session","required":true,"in":"path","schema":{"default":"default"},"description":"WhatsApp session name"}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeInfo"}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/checkNumberStatus":{"get":{"operationId":"ChattingController_checkNumberStatus","summary":"Check number status","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WANumberExistResult"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendContactVcard":{"post":{"operationId":"ChattingController_sendContactVcard","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageContactVcardRequest"}}}},"responses":{"201":{"description":""}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendText":{"get":{"operationId":"ChattingController_sendTextGet","summary":"Send a text message","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"text","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]},"post":{"operationId":"ChattingController_sendText","summary":"Send a text message","parameters":[],"requestBody":{"required":true,Line truncated
{"openapi":"3.1.0","paths":{"/api/{session}/auth/qr":{"get":{"operationId":"AuthController_getQR","summary":"Get QR code for pairing WhatsApp Web.","parameters":[{"name":"session","required":true,"in":"path","schema":{"default":"default"},"description":"WhatsApp session name"},{"name":"format","required":true,"in":"query","schema":{"default":"image","enum":["image","raw"],"type":"string"}}],"responses":{"200":{"content":{"image/png":{"schema":{"type":"string","format":"binary"}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Base64File"},{"$ref":"#/components/schemas/QRCodeValue"}]}}},"description":""}},"tags":["auth"],"security":[{"api_key":[]}]}},"/api/{session}/auth/request-code":{"post":{"operationId":"AuthController_requestCode","summary":"Request authentication code.","parameters":[{"name":"session","required":true,"in":"path","schema":{"default":"default"},"description":"WhatsApp session name"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestCodeRequest"}}}},"responses":{"201":{"description":""}},"tags":["auth"],"security":[{"api_key":[]}]}},"/api/{session}/auth/authorize-code":{"post":{"operationId":"AuthController_authorizeCode","summary":"Send OTP authentication code.","parameters":[{"name":"session","required":true,"in":"path","schema":{"default":"default"},"description":"WhatsApp session name"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OTPRequest"}}}},"responses":{"201":{"description":""}},"tags":["auth"],"security":[{"api_key":[]}]}},"/api/sessions/start":{"post":{"operationId":"SessionsController_start","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStartRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionDTO"}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/stop":{"post":{"operationId":"SessionsController_stop","summary":"Stop session","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionStopRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/logout":{"post":{"operationId":"SessionsController_clean","summary":"Logout from session.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionLogoutRequest"}}}},"responses":{"201":{"description":""}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions":{"get":{"operationId":"SessionsController_list","parameters":[{"name":"all","required":false,"in":"query","example":false,"description":"Return all sessions, including those that are in the STOPPED state.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SessionInfo"}}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/sessions/{session}/me":{"get":{"operationId":"SessionController_getMe","summary":"Get information about the authenticated account","parameters":[{"name":"session","required":true,"in":"path","schema":{"default":"default"},"description":"WhatsApp session name"}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeInfo"}}}}},"tags":["sessions"],"security":[{"api_key":[]}]}},"/api/checkNumberStatus":{"get":{"operationId":"ChattingController_checkNumberStatus","summary":"Check number status","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WANumberExistResult"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendContactVcard":{"post":{"operationId":"ChattingController_sendContactVcard","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageContactVcardRequest"}}}},"responses":{"201":{"description":""}},"tags":["chatting"],"security":[{"api_key":[]}]}},"/api/sendText":{"get":{"operationId":"ChattingController_sendTextGet","summary":"Send a text message","parameters":[{"name":"phone","required":true,"in":"query","schema":{"type":"string"}},{"name":"text","required":true,"in":"query","schema":{"type":"string"}},{"name":"session","required":true,"in":"query","schema":{"default":"default","type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["chatting"],"security":[{"api_key":[]}]},"post":{"operationId":"ChattingController_sendText","summary":"Send a text message","parameters":[],"requestBody":{"required":true,Line truncated
+36 -1
View File
@@ -1,4 +1,6 @@
import random
from pprint import pprint
from time import sleep
import requests
from flask import Flask
@@ -49,6 +51,30 @@ def send_seen(chat_id, message_id, participant):
)
response.raise_for_status()
def start_typing(chat_id):
response = requests.post(
"http://localhost:3000/api/startTyping",
json={
"session": "default",
"chatId": chat_id,
},
)
response.raise_for_status()
def stop_typing(chat_id):
response = requests.post(
"http://localhost:3000/api/stopTyping",
json={
"session": "default",
"chatId": chat_id,
},
)
response.raise_for_status()
def typing(chat_id, seconds):
start_typing(chat_id=chat_id)
sleep(seconds)
stop_typing(chat_id=chat_id)
@app.route("/")
def whatsapp_echo():
@@ -69,7 +95,12 @@ def whatsapp_webhook():
# Payload that we've got
payload = data["payload"]
# The text
text = payload["body"]
text = payload.get("body")
if not text:
# We can't process non-text messages yet
print("No text in message")
print(payload)
return "OK"
# Number in format 1231231231@c.us or @g.us for group
chat_id = payload["from"]
# Message ID - false_11111111111@c.us_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
@@ -79,9 +110,13 @@ def whatsapp_webhook():
# IMPORTANT - Always send seen before sending new message
send_seen(chat_id=chat_id, message_id=message_id, participant=participant)
# Send a text back via WhatsApp HTTP API
typing(chat_id=chat_id, seconds=random.random() * 3)
send_message(chat_id=chat_id, text=text)
# OR reply on the message
typing(chat_id=chat_id, seconds=random.random() * 3)
reply(chat_id=chat_id, message_id=message_id, text=text)
# Send OK back
+26 -1
View File
@@ -12,6 +12,7 @@ import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { SessionManager } from '../core/abc/manager.abc';
import { WhatsappSession } from '../core/abc/session.abc';
import {
CaptchaBody,
OTPRequest,
QRCodeFormat,
QRCodeQuery,
@@ -60,7 +61,7 @@ class AuthController {
@SessionParam session: WhatsappSession,
@Body() request: RequestCodeRequest,
) {
return session.requestCode(request.phoneNumber, request.method);
return session.requestCode(request.phoneNumber, request.method, request);
}
@Post('authorize-code')
@@ -74,6 +75,30 @@ class AuthController {
) {
return session.authorizeCode(request.code);
}
@Get('captcha')
@SessionApiParam
@ApiFileAcceptHeader()
@UseInterceptors(new BufferResponseInterceptor())
@ApiOperation({
summary: 'Get captcha image.',
})
async getCaptcha(@SessionParam session: WhatsappSession): Promise<Buffer> {
const image = await session.getCaptcha();
return image.get();
}
@Post('captcha')
@SessionApiParam
@ApiOperation({
summary: 'Enter captcha code.',
})
saveCaptcha(
@SessionParam session: WhatsappSession,
@Body() body: CaptchaBody,
) {
return session.saveCaptcha(body.code);
}
}
export { AuthController };
+9 -1
View File
@@ -199,7 +199,7 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
public requestCode(phoneNumber: string, method: string) {
public requestCode(phoneNumber: string, method: string, params?: any) {
throw new NotImplementedByEngineError();
}
@@ -213,6 +213,14 @@ export abstract class WhatsappSession {
throw new NotImplementedByEngineError();
}
public getCaptcha(): Promise<QR> {
throw new NotImplementedByEngineError();
}
public saveCaptcha(code: string) {
throw new NotImplementedByEngineError();
}
/**
* Other methods
*/
+14 -5
View File
@@ -101,6 +101,9 @@ const ToEnginePresenceStatus = flipObject(PresenceStatuses);
export class WhatsappSessionNoWebCore extends WhatsappSession {
engine = WAHAEngine.NOWEB;
get listenConnectionEventsFromTheStart() {
return true;
}
sock: any;
store: any;
@@ -170,8 +173,10 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
if (this.isDebugEnabled()) {
this.listenEngineEventsInDebugMode();
}
this.listenConnectionEvents();
this.events.emit(WAHAInternalEvent.ENGINE_START);
if (this.listenConnectionEventsFromTheStart) {
this.listenConnectionEvents();
this.events.emit(WAHAInternalEvent.ENGINE_START);
}
}
protected async getMessage(
@@ -263,6 +268,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
public async requestCode(
phoneNumber: string,
method: string,
params?: any,
): Promise<PairingCodeResponse> {
if (method) {
const err = `NOWEB engine doesn't support any 'method', remove it and try again`;
@@ -279,7 +285,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
throw new UnprocessableEntityException(err);
}
this.log.log('Requesting pairing code...');
this.log.log(`Requesting pairing code for '${phoneNumber}'...`);
const code: string = await this.sock.requestPairingCode(phoneNumber);
// show it as ABCD-ABCD
const parts = splitAt(code, 4);
@@ -312,10 +318,13 @@ export class WhatsappSessionNoWebCore extends WhatsappSession {
): Promise<WANumberExistResult> {
const phone = request.phone.split('@')[0];
const [result] = await this.sock.onWhatsApp(phone);
if (!result) {
if (!result || !result.exists) {
return { numberExists: false };
}
return { numberExists: result.exists };
return {
numberExists: true,
chatId: toCusFormat(result.jid),
};
}
sendText(request: MessageTextRequest) {
+13 -6
View File
@@ -192,16 +192,23 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
async checkNumberStatus(
request: CheckNumberStatusQuery,
): Promise<WANumberExistResult> {
const exist = await this.whatsapp.isRegisteredUser(
this.ensureSuffix(request.phone),
);
return { numberExists: exist };
const phone = request.phone.split('@')[0];
const result = await this.whatsapp.getNumberId(phone);
if (!result) {
return {
numberExists: false,
};
}
return {
numberExists: true,
chatId: result._serialized,
};
}
sendText(request: MessageTextRequest) {
const options = {
// It's fine to sent just ids instead of Contact object
mentions: request.mentions as unknown as Contact[],
mentions: request.mentions as unknown as string[],
};
return this.whatsapp.sendMessage(
this.ensureSuffix(request.chatId),
@@ -214,7 +221,7 @@ export class WhatsappSessionWebJSCore extends WhatsappSession {
const options = {
quotedMessageId: request.reply_to,
// It's fine to sent just ids instead of Contact object
mentions: request.mentions as unknown as Contact[],
mentions: request.mentions as unknown as string[],
};
return this.whatsapp.sendMessage(request.chatId, request.text, options);
}
+11 -1
View File
@@ -1,4 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiHideProperty, ApiProperty } from '@nestjs/swagger';
export enum QRCodeFormat {
IMAGE = 'image',
@@ -26,12 +26,22 @@ export class RequestCodeRequest {
required: false,
})
method: string;
@ApiHideProperty()
localeLanguage: string;
@ApiHideProperty()
localeCountry: string;
}
export class OTPRequest {
code: string;
}
export class CaptchaBody {
code: string;
}
export class PairingCodeResponse {
code: string;
}
+5
View File
@@ -169,6 +169,11 @@ export class MessageReactionRequest extends MessageRequest {
export class WANumberExistResult {
numberExists: boolean;
@ApiProperty({
example:
'Chat id for the phone number. Undefined if the number does not exist',
})
chatId?: string;
}
export class MessagePoll {
+1 -1
View File
@@ -26,7 +26,7 @@ export enum WAHAEngine {
VENOM = 'VENOM',
WEBJS = 'WEBJS',
NOWEB = 'NOWEB',
NOWEB_MOBILE = 'NOWEB_MOBILE',
MOBILE = 'MOBILE',
}
export enum WAHAPresenceStatus {
+3 -3
View File
@@ -21,13 +21,13 @@ export class WAMedia {
description: 'mimetype for the media in the message if any',
example: 'audio/jpeg',
})
mimetype: string | null;
mimetype?: string;
@ApiProperty({
description: 'The original filename in mediaUrl in the message if any',
example: 'example.pdf',
})
filename: string | null;
filename?: string;
}
export class WAMessage {
@@ -76,7 +76,7 @@ export class WAMessage {
})
hasMedia: boolean;
media: WAMedia | null = null;
media?: WAMedia = null;
@ApiProperty({
description:
+3 -3
View File
@@ -84,8 +84,8 @@ export class PollVotePayload {
}
export class WAMessageRevokedBody {
after: WAMessage | null;
before: WAMessage | null;
after?: WAMessage;
before?: WAMessage;
}
export class WASessionStatusBody {
@ApiProperty({
@@ -107,7 +107,7 @@ export class WAHAWebhook {
})
engine: WAHAEngine;
me: MeInfo | null;
me?: MeInfo;
environment: WAHAEnvironment;
+1 -1
View File
@@ -33,7 +33,7 @@ export function getEngineName(): string {
}
export const VERSION: WAHAEnvironment = {
version: '2023.12.1',
version: '2023.12.3',
engine: getEngineName(),
tier: getWAHAVersion(),
browser: getBrowserExecutablePath(),
+43 -47
View File
@@ -13,28 +13,28 @@ __metadata:
linkType: hard
"@adiwajshing/baileys@github:WhiskeySockets/Baileys":
version: 6.5.0
resolution: "@adiwajshing/baileys@https://github.com/WhiskeySockets/Baileys.git#commit=e24947e55ad6e3370de867b258bb33bb9e828c56"
version: 6.6.0
resolution: "@adiwajshing/baileys@https://github.com/WhiskeySockets/Baileys.git#commit=daafc122759bd5b54279f82b196e1ce41aec4807"
dependencies:
"@adiwajshing/keyed-db": ^0.2.4
"@hapi/boom": ^9.1.3
audio-decode: ^2.1.3
axios: ^1.3.3
cache-manager: ^5.2.2
cache-manager: 4.0.1
futoin-hkdf: ^1.5.1
libphonenumber-js: ^1.10.20
libsignal: "github:adiwajshing/libsignal-node"
music-metadata: ^7.12.3
node-cache: ^5.1.2
pino: ^7.0.0
protobufjs: ^6.11.3
protobufjs: ^7.2.4
uuid: ^9.0.0
ws: ^8.13.0
peerDependencies:
jimp: ^0.16.1
link-preview-js: ^3.0.0
qrcode-terminal: ^0.12.0
sharp: ^0.30.5
sharp: ^0.32.2
peerDependenciesMeta:
jimp:
optional: true
@@ -44,7 +44,7 @@ __metadata:
optional: true
sharp:
optional: true
checksum: b15ff56171157df1d4b55e984c09bf67a5537a179be57bf36e2e97cbdeaf70f1d3ee1dfb3e9d95a916b3e44373dc5f35a9bb8481e9c49bbba83b5ec926b6755d
checksum: 252146db962934c5dd54472922dd6b60725879f1fd1a3809e464fef726738cb6deeab843394f9793ca98f23d74e4a1436bca5f2a8ab7534e0ea668f3a117011a
languageName: node
linkType: hard
@@ -1650,7 +1650,7 @@ __metadata:
languageName: node
linkType: hard
"@types/long@npm:^4.0.0, @types/long@npm:^4.0.1":
"@types/long@npm:^4.0.0":
version: 4.0.2
resolution: "@types/long@npm:4.0.2"
checksum: d16cde7240d834cf44ba1eaec49e78ae3180e724cd667052b194a372f350d024cba8dd3f37b0864931683dab09ca935d52f0c4c1687178af5ada9fc85b0635f4
@@ -2798,6 +2798,13 @@ __metadata:
languageName: node
linkType: hard
"async@npm:3.2.3":
version: 3.2.3
resolution: "async@npm:3.2.3"
checksum: c4bee57ab2249af3dc83ca3ef9acfa8e822c0d5e5aa41bae3eaf7f673648343cd64ecd7d26091ffd357f3f044428b17b5f00098494b6cf8b6b3e9681f0636ca1
languageName: node
linkType: hard
"async@npm:>=0.2.9, async@npm:^3.2.3, async@npm:^3.2.4":
version: 3.2.4
resolution: "async@npm:3.2.4"
@@ -3351,13 +3358,14 @@ __metadata:
languageName: node
linkType: hard
"cache-manager@npm:^5.2.2":
version: 5.2.3
resolution: "cache-manager@npm:5.2.3"
"cache-manager@npm:4.0.1":
version: 4.0.1
resolution: "cache-manager@npm:4.0.1"
dependencies:
async: 3.2.3
lodash.clonedeep: ^4.5.0
lru-cache: ^9.1.2
checksum: 2bcd6a385e5575bf9577a209e02c9f5074cd476e6eb08a0f108e45d80c20f4e91fbff163493fe345f166f8ecb232687f20541bc4f42fb2167f8770f9f8b26c51
lru-cache: ^7.10.1
checksum: 8ca25b1e603111f033d1a3f1463b2a9203e9ea61a0706265a4d4f63d54a8be95f9e79efb04fc43103219becb4903a5db747a346e009e7102767a47e5677219b2
languageName: node
linkType: hard
@@ -5706,7 +5714,7 @@ __metadata:
languageName: node
linkType: hard
"fluent-ffmpeg@npm:^2.1.2":
"fluent-ffmpeg@npm:2.1.2":
version: 2.1.2
resolution: "fluent-ffmpeg@npm:2.1.2"
dependencies:
@@ -8063,13 +8071,6 @@ __metadata:
languageName: node
linkType: hard
"jsqr@npm:^1.3.1":
version: 1.4.0
resolution: "jsqr@npm:1.4.0"
checksum: 7c572971f90c42772e30d152bde63b84edf1164bde80c53942e6b2068ea31caf00ad704aa46cacc9e71645f52dbeddebc6e84ba15e883c678ee93cde690de339
languageName: node
linkType: hard
"keyv@npm:^3.0.0":
version: 3.1.0
resolution: "keyv@npm:3.1.0"
@@ -8437,6 +8438,13 @@ __metadata:
languageName: node
linkType: hard
"long@npm:^5.0.0":
version: 5.2.3
resolution: "long@npm:5.2.3"
checksum: 885ede7c3de4facccbd2cacc6168bae3a02c3e836159ea4252c87b6e34d40af819824b2d4edce330bfb5c4d6e8ce3ec5864bdcf9473fa1f53a4f8225860e5897
languageName: node
linkType: hard
"longest@npm:^2.0.1":
version: 2.0.1
resolution: "longest@npm:2.0.1"
@@ -8476,7 +8484,7 @@ __metadata:
languageName: node
linkType: hard
"lru-cache@npm:^7.14.1, lru-cache@npm:^7.7.1":
"lru-cache@npm:^7.10.1, lru-cache@npm:^7.14.1, lru-cache@npm:^7.7.1":
version: 7.18.3
resolution: "lru-cache@npm:7.18.3"
checksum: e550d772384709deea3f141af34b6d4fa392e2e418c1498c078de0ee63670f1f46f5eee746e8ef7e69e1c895af0d4224e62ee33e66a543a14763b0f2e74c1356
@@ -8490,13 +8498,6 @@ __metadata:
languageName: node
linkType: hard
"lru-cache@npm:^9.1.2":
version: 9.1.2
resolution: "lru-cache@npm:9.1.2"
checksum: d3415634be3908909081fc4c56371a8d562d9081eba70543d86871b978702fffd0e9e362b83921b27a29ae2b37b90f55675aad770a54ac83bb3e4de5049d4b15
languageName: node
linkType: hard
"lunr@npm:^2.3.9":
version: 2.3.9
resolution: "lunr@npm:2.3.9"
@@ -9240,10 +9241,10 @@ __metadata:
languageName: node
linkType: hard
"node-webpmux@npm:^3.1.0":
version: 3.1.8
resolution: "node-webpmux@npm:3.1.8"
checksum: 96d230f33bf645e93c4e1c16f506571bde91ede320d4a9951ed08a099f5796b68a89a3591815019eee3c385215eaaa6c428008abea65cecabbfbd463b8551b23
"node-webpmux@npm:3.1.0":
version: 3.1.0
resolution: "node-webpmux@npm:3.1.0"
checksum: 45041b561d21ab88b81b5eaa1761cf1667dc56118b1ecf1ec1774d5f00ab9fbf473cdada7d5e09577f12dbc765fd22841b81ebeee352ddba5db7b6c5f5980056
languageName: node
linkType: hard
@@ -10084,9 +10085,9 @@ __metadata:
languageName: node
linkType: hard
"protobufjs@npm:^6.11.3":
version: 6.11.4
resolution: "protobufjs@npm:6.11.4"
"protobufjs@npm:^7.2.4":
version: 7.2.6
resolution: "protobufjs@npm:7.2.6"
dependencies:
"@protobufjs/aspromise": ^1.1.2
"@protobufjs/base64": ^1.1.2
@@ -10098,13 +10099,9 @@ __metadata:
"@protobufjs/path": ^1.1.2
"@protobufjs/pool": ^1.1.0
"@protobufjs/utf8": ^1.1.0
"@types/long": ^4.0.1
"@types/node": ">=13.7.0"
long: ^4.0.0
bin:
pbjs: bin/pbjs
pbts: bin/pbts
checksum: b2fc6a01897b016c2a7e43a854ab4a3c57080f61be41e552235436e7a730711b8e89e47cb4ae52f0f065b5ab5d5989fc932f390337ce3a8ccf07203415700850
long: ^5.0.0
checksum: 3c62e48f7d50017ac3b0dcd2a58e617cf858f9fba56a488bd48b9aa3482893a75540052dbcb3c12dfbaab42b1d04964611175faf06bdadcd33a4ebac982a511e
languageName: node
linkType: hard
@@ -13130,17 +13127,16 @@ __metadata:
linkType: soft
"whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js":
version: 1.23.1-alpha.0
resolution: "whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js.git#commit=58fc39f97d5663f7651faff4c60c12f2e91ce63e"
version: 1.23.1-alpha.4
resolution: "whatsapp-web.js@https://github.com/pedroslopez/whatsapp-web.js.git#commit=ef2f725bce1bd1afd8491f9de0cd2ed06a6febc3"
dependencies:
"@pedroslopez/moduleraid": ^5.0.2
archiver: ^5.3.1
fluent-ffmpeg: ^2.1.2
fluent-ffmpeg: 2.1.2
fs-extra: ^10.1.0
jsqr: ^1.3.1
mime: ^3.0.0
node-fetch: ^2.6.5
node-webpmux: ^3.1.0
node-webpmux: 3.1.0
puppeteer: ^13.0.0
unzipper: ^0.10.11
dependenciesMeta:
@@ -13150,7 +13146,7 @@ __metadata:
optional: true
unzipper:
optional: true
checksum: ac8b24ccc79f26dfe28c6ae3b39fa18dae6e3ea3b3831358eaf84e78fa78e67e05adfa1a37ddb6684c88de89a4244d9b589bad375d0ce2e3e9d197f281d15de6
checksum: 36062f9aa97e5d3f2ae50235a1535ae4b67550ea40f875794590cbaed6045b6b5ae56bd3edd79092419018f5509ff8dfb8f03cb894ed76315f9583d546a4f4be
languageName: node
linkType: hard