whatsapp-adb-container/README.md
2026-07-22 20:40:10 +02:00

409 lines
12 KiB
Markdown

# WhatsApp ADB Container
Appium 3 + UiAutomator2-driver in Docker, voor geautomatiseerde aansturing van een fysieke Android-telefoon
via ADB. Ontworpen voor de **Nexus 6P** (niet geroot) met WhatsApp.
Twee containers:
| Container | Functie | Poort |
|-----------|---------|-------|
| `appium-adb` | ADB-server + Appium | `:4723` |
| `wa-appium-api` | Express REST API (fallback voor wwebjs-api) | `:3010` |
---
## Architectuur
```
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ appium-adb │ │ wa-appium-api │
│ │ │ │
│ entrypoint.sh: ────────────┤ │ server.js: │
│ adb start-server │ │ Express REST API │
│ adb connect/usb │ │ WebDriverIO → Appium │
│ wifi_sleep_policy=2 │ │ /client/getChats/:ses │
│ svc power stayon usb │ │ /chat/fetchMessages/:ses │
│ dumpsys deviceidle off │ │ /client/sendMessage/:ses │
│ reconnect loop (30s) │ │ │
│ appium server :4723 ─────┼─────┤ localhost:4723 │
│ │ │ │
└─────────────────────────────┘ └─────────────────────────────┘
│ USB │ REST (localhost:3010)
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Nexus 6P │ │ Clients │
│ (ENU7N15B…) │ │ (scripts, │
│ WhatsApp │ │ curl, etc) │
└──────────────┘ └──────────────┘
```
**Fallback-strategie:** De primaire WhatsApp-laag is [[Whatsapp rest-api|wwebjs-api]] (`:3001`)
via WhatsApp Web. Als die sessie wegvalt (geblokkeerd nummer, browser-crash), springt
wa-appium-api (`:3010`) in — die de telefoon direct via USB aanstuurt.
---
## Snelstart
### USB-modus (telefoon aan de kabel)
Zet USB debugging aan op de telefoon (Settings > Developer options > USB debugging).
```bash
# check of de telefoon zichtbaar is
adb devices
# → ENU7N15B14000616 device
# start beide containers
docker compose up -d
# smoke test
node smoke-test.js
# health check
curl http://127.0.0.1:3010/health
```
### TCP/IP-modus (telefoon remote)
```bash
# eenmalig na elke reboot (kabel nodig)
adb usb && adb tcpip 5555
# kabel los, start met PHONE_IP
PHONE_IP=192.168.5.100 docker compose up -d
```
---
## Environment variabelen
### appium-adb
| Variabele | Default | Beschrijving |
|------------|---------|-------------|
| `PHONE_IP` | *(leeg)* | IP voor TCP/IP-modus. Leeg = USB-modus |
| `ADB_PORT` | `5555` | ADB-poort (na `adb tcpip <poort>`) |
### wa-appium-api
| Variabele | Default | Beschrijving |
|---------------|-------------|-------------|
| `APPIUM_HOST` | `127.0.0.1` | Appium server host |
| `APPIUM_PORT` | `4723` | Appium server poort |
| `PORT` | `3010` | REST API luisterpoort |
Kopieer `.env.example` naar `.env` om defaults aan te passen.
---
## USB-passthrough
De compose mount `/dev/bus/usb` in de `appium-adb` container. De container draait als root,
dus USB-devices zijn direct toegankelijk. Het ADB-serverproces detecteert USB-hotplug
automatisch.
**Eerste keer op een nieuwe host:**
1. udev-regel aanmaken: `SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", MODE="0660", GROUP="plugdev"`
2. Gebruiker toevoegen aan `plugdev`-groep
3. Kabel los/koppelen of `chown root:plugdev /dev/bus/usb/XXX/YYY`
4. RSA-vingerafdruk accepteren op de telefoon ("Altijd toestaan")
---
## wa-appium-api (fallback)
REST API die wwebjs-api-endpoints spiegelt, maar via Appium de Android-telefoon aanstuurt
in plaats van WhatsApp Web. Trager en layout-gevoelig, maar werkt ook als de Web-sessie
geblokkeerd is.
### Endpoints
#### `GET /health`
```bash
curl http://127.0.0.1:3010/health
{"status":"ok","sessionId":"..."}
```
#### `GET /client/getChats/:session`
Haalt alle chats op van het WhatsApp-hoofdscherm.
```bash
curl http://127.0.0.1:3010/client/getChats/Eddy
{
"chats": [
{
"name": "+31 6 12345678",
"lastMessage": "Foto",
"timestamp": "17:28",
"unread": 0
},
...
]
}
```
| Veld | Type | Beschrijving |
|------|------|-------------|
| `name` | string | Contactnaam of telefoonnummer |
| `lastMessage` | string | Laatste bericht preview (leeg bij media) |
| `timestamp` | string | Tijdstip laatste bericht |
| `unread` | int | Aantal ongelezen berichten |
**Limitaties:** alleen zichtbare chats op het scherm. Scrollen wordt nog niet ondersteund.
Media-berichten tonen lege preview of `NaN`.
#### `POST /chat/fetchMessages/:session`
Haalt berichten op uit een specifieke chat.
```bash
curl -X POST http://127.0.0.1:3010/chat/fetchMessages/Eddy \
-H 'Content-Type: application/json' \
-d '{"chatId": "31612345678"}'
{
"messages": [
{
"text": "Hallo!",
"timestamp": "10:00"
},
...
]
}
```
| Parameter | Type | Verplicht | Beschrijving |
|-----------|------|-----------|-------------|
| `chatId` | string | ja | Telefoonnummer (met of zonder `@c.us`) of contactnaam |
**Limitaties:** alleen tekstberichten worden opgehaald (geen media, voice, of system
messages). Alleen zichtbare berichten in de weergave (scrollt niet).
#### `POST /client/sendMessage/:session`
Verstuurt een tekstbericht.
```bash
curl -X POST http://127.0.0.1:3010/client/sendMessage/Eddy \
-H 'Content-Type: application/json' \
-d '{
"chatId": "31612345678",
"content": "Hallo vanuit de API!"
}'
{"status":"sent","content":"Hallo vanuit de API!"}
```
| Parameter | Type | Verplicht | Beschrijving |
|-----------|------|-----------|-------------|
| `chatId` | string | ja | Telefoonnummer (zonder `@c.us`) of contactnaam |
| `content` | string | ja | Berichttekst |
**Werkt met:** individuele chats. Groepsberichten nog niet getest.
### Hoe het werkt (intern)
De API gebruikt WebDriverIO om via Appium de WhatsApp Android-app aan te sturen:
```
getChats: startActivity → lees contact_row_container elementen → parse naam/bericht/tijd/ongelezen
openChat: startActivity → klik search_bar_inner_layout → type in search_input → klik contact_row_container
sendMessage: openChat → setValue in entry → klik send → goHome
fetchMessages: openChat → lees main_layout → message_text + date
```
**Gebruikte resource-IDs** (WhatsApp v2.25+, kunnen wijzigen per versie):
| Resource-ID | Functie |
|-------------|---------|
| `com.whatsapp:id/search_bar_inner_layout` | Zoekbalk op home |
| `com.whatsapp:id/search_input` | Zoekveld |
| `com.whatsapp:id/contact_row_container` | Chatrij in lijst |
| `com.whatsapp:id/conversations_row_contact_name` | Contactnaam |
| `com.whatsapp:id/single_msg_tv` | Bericht preview (individueel) |
| `com.whatsapp:id/msg_from_tv` | Bericht preview (community/groep) |
| `com.whatsapp:id/conversations_row_date` | Tijdstip |
| `com.whatsapp:id/conversations_row_unread_indicator` | Ongelezen indicator |
| `com.whatsapp:id/entry` | Tekstinvoerveld |
| `com.whatsapp:id/send` | Verzendknop |
| `com.whatsapp:id/main_layout` | Berichtcontainer in chat |
| `com.whatsapp:id/message_text` | Berichttekst |
| `com.whatsapp:id/date` | Datum/tijd van bericht |
---
## Containers in detail
### appium-adb
**Image build:**
- Base: `node:lts-bookworm-slim`
- OpenJDK 17 + Android platform-tools (Google) + Appium 3.x + uiautomator2-driver
- Zie `Dockerfile`
**Entrypoint (`entrypoint.sh`):**
1. `adb start-server`
2. Detecteer USB (`adb -d`) of TCP (`adb connect`)
3. Pas instellingen toe (non-root): `wifi_sleep_policy=2`, `svc power stayon usb`, `deviceidle disable`
4. Start reconnect loop op achtergrond (elke 30s check of device nog zichtbaar is)
5. `exec appium --address 0.0.0.0 --port 4723 --allow-cors --relaxed-security`
### wa-appium-api
**Image build:**
- Base: `node:lts-bookworm-slim`
- Express + WebDriverIO
- Zie `wa-appium-api/Dockerfile`
**Belangrijke code in `wa-appium-api/server.js`:**
- `getDriver()` — maakt Appium-sessie, herconnect bij drop
- `ensureWhatsApp(d)` — start WhatsApp als het niet op de voorgrond staat
- `goHome(d)` — navigeer naar WhatsApp home
- `openChat(d, query)` — zoek chat via search, open 'm
- Finders gebruiken UiAutomator2 `new UiSelector()` via WebDriverIO
---
## Troubleshooting
### "Could not find a connected Android device"
```
docker logs appium-adb — check of adb connect/usb lukt
```
- Zit de USB-kabel er goed in? Check met `adb devices` op de **host**.
- USB debugging aan op de telefoon? (Developer options)
- Nieuwe RSA-key? Kabel los/koppelen en "Altijd toestaan" op telefoon.
- Voor TCP/IP: `adb connect <IP>:5555` testen vanaf host.
### ADB-serverversie-mismatch
ADB in de container (Google platform-tools) en op de host kunnen verschillen.
Meestal geen probleem. Bij vreemd gedrag:
```bash
# host ADB updaten
sudo -A dnf install android-tools # Fedora
# of gebruik ADB uit container
docker exec appium-adb adb devices
```
### Appium vindt uiautomator2-driver niet
```
driver uiautomator2 is not installed
```
```bash
docker exec appium-adb appium driver install uiautomator2
# of rebuild
docker compose build --no-cache appium
```
### Telefoon niet meer bereikbaar na reboot
Na een reboot moet USB debugging opnieuw bevestigd worden (RSA-vingerafdruk):
```bash
adb devices
# zo niet: kabel los/koppelen, bevestig op telefoon
# voor TCP/IP: adb usb && adb tcpip 5555
```
### Verbinding valt weg na verloop van tijd
De entrypoint past toe:
```
settings put global wifi_sleep_policy 2
svc power stayon usb
dumpsys deviceidle disable
```
Bij USB-verbinding zou dit zelden moeten gebeuren. Check:
1. `docker logs appium-adb` — zie je reconnect-pogingen?
2. **Kabel:** probeer een andere. Goedkope kabels geven drops.
3. **USB-poort:** direct op moederbord, niet via hub.
4. **Stay awake:** zet aan in Developer options op de telefoon.
5. **Settings opnieuw:** `docker restart appium-adb`.
6. **Laatste redmiddel:** rooten + `iw wlan0 set power_save off` (alleen TCP/IP).
### wa-appium-api fault
```bash
docker logs wa-appium-api
# als server.js is aangepast, rebuild nodig:
docker compose build wa-appium-api
docker compose up -d wa-appium-api
```
### wa-appium-api: session lost
De `getDriver()` functie herconnect automatisch bij een verbroken sessie.
Als dat niet werkt:
```bash
docker restart wa-appium-api
```
---
## Onderhoud
### Rebuild en restart
```bash
# alles
docker compose build --no-cache
docker compose up -d
# alleen Appium (bv. na driver update)
docker compose build --no-cache appium
docker compose up -d appium
# alleen API (bv. na code wijziging)
docker compose build wa-appium-api
docker compose up -d wa-appium-api
```
### Logs
```bash
docker logs appium-adb -f
docker logs wa-appium-api -f
```
### Phone settings resetten
```bash
docker exec appium-adb adb shell settings put global wifi_sleep_policy 2
docker exec appium-adb adb shell svc power stayon usb
docker exec appium-adb adb shell dumpsys deviceidle disable
```
---
## Bestanden
```
├── Dockerfile # Image: Node LTS + Java 17 + adb + Appium
├── docker-compose.yml # Service-definities (appium + wa-appium-api)
├── entrypoint.sh # USB/TCP-detectie + settings + reconnect-loop
├── wa-appium-api/
│ ├── Dockerfile # Image: Node LTS + Express + WebDriverIO
│ ├── package.json # Dependencies
│ └── server.js # Express REST API
├── smoke-test.js # Node.js smoke-test (geen dependencies)
├── .env.example # Voorbeeld env-variabelen
└── README.md # Dit bestand
```
## Zie ook
- [[Whatsapp rest-api]] — primaire WhatsApp API (wwebjs-api, `:3001`)
- [[Android Telefoon]] — Nexus 6P met Termux
- [[Whatsapp Scripts Migratie]] — overzicht WhatsApp automatisering