No description
  • JavaScript 69.3%
  • Kotlin 21.2%
  • Shell 6.4%
  • Dockerfile 3.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-07-22 20:40:10 +02:00
wa-appium-api Initial commit 2026-07-22 20:40:10 +02:00
.env.example Initial commit 2026-07-22 20:40:10 +02:00
.gitignore Initial commit 2026-07-22 20:40:10 +02:00
docker-compose.yml Initial commit 2026-07-22 20:40:10 +02:00
Dockerfile Initial commit 2026-07-22 20:40:10 +02:00
entrypoint.sh Initial commit 2026-07-22 20:40:10 +02:00
README.md Initial commit 2026-07-22 20:40:10 +02:00
smoke-test.js Initial commit 2026-07-22 20:40:10 +02:00

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:

  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

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):

  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:

# 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:

  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

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