- JavaScript 69.3%
- Kotlin 21.2%
- Shell 6.4%
- Dockerfile 3.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| wa-appium-api | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| entrypoint.sh | ||
| README.md | ||
| smoke-test.js | ||
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 (: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).
# 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)
# 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:
- udev-regel aanmaken:
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", MODE="0660", GROUP="plugdev" - Gebruiker toevoegen aan
plugdev-groep - Kabel los/koppelen of
chown root:plugdev /dev/bus/usb/XXX/YYY - 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
curl http://127.0.0.1:3010/health
{"status":"ok","sessionId":"..."}
GET /client/getChats/:session
Haalt alle chats op van het WhatsApp-hoofdscherm.
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.
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.
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):
adb start-server- Detecteer USB (
adb -d) of TCP (adb connect) - Pas instellingen toe (non-root):
wifi_sleep_policy=2,svc power stayon usb,deviceidle disable - Start reconnect loop op achtergrond (elke 30s check of device nog zichtbaar is)
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 dropensureWhatsApp(d)— start WhatsApp als het niet op de voorgrond staatgoHome(d)— navigeer naar WhatsApp homeopenChat(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 devicesop 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>:5555testen vanaf host.
ADB-serverversie-mismatch
ADB in de container (Google platform-tools) en op de host kunnen verschillen. Meestal geen probleem. Bij vreemd gedrag:
# 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
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):
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:
docker logs appium-adb— zie je reconnect-pogingen?- Kabel: probeer een andere. Goedkope kabels geven drops.
- USB-poort: direct op moederbord, niet via hub.
- Stay awake: zet aan in Developer options op de telefoon.
- Settings opnieuw:
docker restart appium-adb. - Laatste redmiddel: rooten +
iw wlan0 set power_save off(alleen TCP/IP).
wa-appium-api fault
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:
docker restart wa-appium-api
Onderhoud
Rebuild en restart
# 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
docker logs appium-adb -f
docker logs wa-appium-api -f
Phone settings resetten
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