Initial commit
This commit is contained in:
commit
ccd9f0ddd0
1 changed files with 995 additions and 0 deletions
409
README.md
Normal file
409
README.md
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
# 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
|
||||
Loading…
Reference in a new issue