{"id":50995743,"url":"https://github.com/omercnet/yawab","last_synced_at":"2026-06-20T09:01:34.310Z","repository":{"id":364738622,"uuid":"1268899480","full_name":"omercnet/yawab","owner":"omercnet","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-14T10:13:23.000Z","size":578,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-14T10:17:07.352Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/omercnet.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-14T04:15:27.000Z","updated_at":"2026-06-14T09:31:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/omercnet/yawab","commit_stats":null,"previous_names":["omercnet/yawab"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/omercnet/yawab","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omercnet%2Fyawab","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omercnet%2Fyawab/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omercnet%2Fyawab/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omercnet%2Fyawab/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/omercnet","download_url":"https://codeload.github.com/omercnet/yawab/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omercnet%2Fyawab/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34563537,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-20T09:01:33.470Z","updated_at":"2026-06-20T09:01:34.296Z","avatar_url":"https://github.com/omercnet.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Yawab\n\nA cross-platform desktop app that pairs with your WhatsApp account over a QR\ncode and sends a personalised message to a list of contacts loaded from a CSV\nfile. Built with **Electron + React + TypeScript** and\n[Baileys](https://github.com/WhiskeySockets/Baileys).\n\n\u003e ⚠️ **Use responsibly.** Sending unsolicited bulk messages can violate\n\u003e WhatsApp's Terms of Service and may get your number banned. Only message\n\u003e people who have opted in, keep volumes modest, and respect the randomized\n\u003e sending delays. You are responsible for how you use this tool.\n\n## Features\n\n- 📱 **Pair over QR** — link the app to your WhatsApp like WhatsApp Web; the\n  session is stored locally so you only scan once.\n- 📄 **CSV import** — auto-detects the phone and name columns, normalizes phone\n  numbers to E.164, de-duplicates, and reports skipped rows.\n- ✍️ **Templated messages** — personalise with `{{name}}`, `{{phone}}`, or any\n  other CSV column via `{{column}}` tokens, with a live preview.\n- 🐢 **Safe pacing** — randomized delay between messages to reduce ban risk,\n  with live progress and a cancel button.\n- 🖥️ **Cross-platform** — packaged installers for macOS (`.dmg`) and Windows\n  (`.exe`/NSIS) via GitHub Actions.\n- 🌐 **Localized** — English, Hebrew, Russian, Arabic, and Spanish, with full\n  RTL support. Picks the OS language on first run; switchable in the header.\n- ♿ **Accessible** — semantic markup, labelled controls, keyboard focus styles,\n  and a native progress element.\n- 🧹 **Biome** (strict ruleset) for linting/formatting, **Vitest** for tests.\n\n## How it works\n\n```\n┌──────────┐    QR pair    ┌────────────────┐   send    ┌──────────┐\n│  React   │ ◀───────────▶ │  Baileys (main │ ────────▶ │ WhatsApp │\n│ renderer │   IPC bridge  │    process)    │           │  servers │\n└──────────┘               └────────────────┘           └──────────┘\n```\n\nAll WhatsApp logic runs in the Electron **main** process (`src/main`). The\n**renderer** (`src/renderer`) talks to it through a typed, context-isolated\npreload bridge (`src/preload`). Pure, framework-free business logic lives in\n`src/shared` and is fully unit-tested.\n\n## Getting started\n\n```bash\nnpm install      # installs deps and rebuilds native modules\nnpm run dev      # launch the app in development with hot reload\n```\n\nThen:\n\n1. Click **Connect** and scan the QR code with WhatsApp →\n   *Settings → Linked Devices → Link a Device*.\n2. Upload a CSV (see [`examples/contacts.example.csv`](examples/contacts.example.csv)).\n3. Compose your message using tokens like `{{name}}`.\n4. Review and **Start sending**.\n\n### CSV format\n\nThe phone column is auto-detected from common headers (`phone`, `number`,\n`mobile`, `whatsapp`, …). A `name` column is optional. Any other columns become\ntemplate tokens.\n\n```csv\nname,phone,company\nAda Lovelace,+1 415 555 2671,Analytical Engines\nGrace Hopper,+1 415 555 9000,US Navy\n```\n\n## Scripts\n\n| Command | Description |\n| --- | --- |\n| `npm run dev` | Run the app with hot reload |\n| `npm run build` | Typecheck + build main/preload/renderer |\n| `npm test` | Run the Vitest unit suite |\n| `npm run test:coverage` | Run tests with coverage thresholds |\n| `npm run test:e2e` | Run the Playwright E2E suite (build first) |\n| `npm run check` | Biome lint + format check |\n| `npm run format` | Biome format (write) |\n| `npm run typecheck` | TypeScript checks (node + web) |\n| `npm run build:mac` | Build a macOS `.dmg` |\n| `npm run build:win` | Build a Windows installer |\n| `npm run build:linux` | Build a Linux AppImage + `.deb` |\n\n## Testing\n\nTwo layers, both run in CI:\n\n- **Unit (Vitest)** — the pure `src/shared` logic (phone normalization, CSV\n  parsing, templating, throttling, locale resolution) at 100% line coverage.\n- **End-to-end (Playwright + Electron)** — `e2e/` drives the *built* app through\n  the entire wizard (pair → upload → compose → send), plus language switching,\n  RTL direction, and preference persistence across restarts. WhatsApp is\n  replaced by a deterministic fake (`FakeWhatsAppService`, enabled with the\n  `YAWAB_FAKE_WA` env flag) so no real account or network is needed.\n- **Visual regression (Playwright screenshots)** — `e2e/visual.spec.ts`\n  snapshots every screen, including the Hebrew/Arabic RTL layouts. Baselines\n  live in `e2e/visual.spec.ts-snapshots/` and are platform-suffixed (`-linux`);\n  CI and local runs use the same Noto fonts so rendering matches. Update\n  intentionally with `--update-snapshots`.\n\n```bash\nnpm run build \u0026\u0026 npm run test:e2e                       # run everything\nnpm run build \u0026\u0026 npx playwright test --update-snapshots  # refresh baselines\n```\n\n\u003e Visual baselines are Linux-rendered. Regenerate them on Linux (or in CI) —\n\u003e macOS/Windows produce their own platform-suffixed snapshots.\n\n## Releases \u0026 auto-update\n\nReleases are automated with\n[release-please](https://github.com/googleapis/release-please). It watches\nConventional Commits on `main` and keeps a **release PR** open; merging that PR\nbumps the version, tags it, and the publish job in `release-please.yml` builds and\nuploads **macOS (`.dmg`), Windows (NSIS), and Linux (AppImage + `.deb`)** artifacts\nto the GitHub Release across a macOS/Windows/Linux runner matrix.\n\nTo enable macOS code signing / notarization, set the `CSC_LINK`,\n`CSC_KEY_PASSWORD`, `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, and\n`APPLE_TEAM_ID` repository secrets (see the macOS troubleshooting section below).\n\nOn launch, production builds check GitHub Releases for a newer version via\n[`electron-updater`](https://www.electron.build/auto-update) and notify the user\n(no-op in dev and tests).\n\n### macOS: “Yawab.app is damaged and can’t be opened”\n\nThis is **macOS Gatekeeper**, not a corrupt download. Builds that aren’t signed\nwith an Apple **Developer ID** and **notarized** are quarantined when downloaded,\nand on Apple Silicon that surfaces as the “damaged” message.\n\n**The real fix — a signed + notarized build (no warning, no Terminal):** the\nrelease pipeline is already wired for it (`hardenedRuntime` and entitlements are\nset), so adding these repository secrets makes every published `.dmg` open with\na normal double-click:\n\n| Secret | What it is |\n| --- | --- |\n| `CSC_LINK` | base64 of your *Developer ID Application* certificate (`.p12`) |\n| `CSC_KEY_PASSWORD` | password for that `.p12` |\n| `APPLE_ID` | your Apple ID email |\n| `APPLE_APP_SPECIFIC_PASSWORD` | an app-specific password for that Apple ID |\n| `APPLE_TEAM_ID` | your Apple Developer Team ID |\n\nThis needs an **Apple Developer Program** membership ($99/yr); there is no free\nway to remove the warning entirely.\n\n**Until a signed build ships, to open the current build:**\n\n- **Reliable:** `xattr -cr /Applications/Yawab.app` (clears the quarantine flag).\n- **GUI** (works for a plain “unidentified developer” prompt, less reliably for\n  “damaged”): **System Settings → Privacy \u0026 Security → Open Anyway**.\n\n## Internationalization\n\nUI strings live in `src/renderer/src/locales/\u003clang\u003e.json` (one file per language:\n`en`, `he`, `ru`, `ar`, `es`) and are loaded through\n[i18next](https://www.i18next.com/) / `react-i18next`. The main process resolves\nthe language on startup from the persisted preference or the OS locale\n(`app.getLocale()`), and the renderer applies the correct text `direction`\n(LTR/RTL) to the document. Users can override the language from the selector in\nthe header; the choice is persisted in `settings.json` under the app's userData\ndirectory.\n\nTo add a language: add its metadata to `SUPPORTED_LANGUAGES` in\n`src/shared/locales.ts` and drop a matching `\u003ccode\u003e.json` into the locales\nfolder.\n\n## Project layout\n\n```\nsrc/\n  main/        Electron main process — lifecycle, IPC, Baileys service,\n               fake service (E2E), settings, auto-update\n  preload/     Context-isolated bridge exposing window.api\n  renderer/    React UI + i18n (connect → contacts → compose → send)\n  shared/      Pure logic: phone, csv, template, throttle, locales (unit-tested)\ne2e/           Playwright end-to-end specs + CSV fixtures\n.github/\n  workflows/   CI (lint/typecheck/unit/e2e/build) and Release (mac/win/linux)\nwebsite/        Marketing site (Vite + React), deployed to GitHub Pages\n```\n\n## Website\n\nThe landing page lives in [`website/`](website/) and is deployed to\n**https://yawab.app** via GitHub Actions. Its download buttons\nresolve the latest GitHub release at runtime, so they always point at the newest\ninstallers with no redeploy. See [`website/README.md`](website/README.md).\n\n## Acknowledgements\n\nProject conventions — electron-vite scaffolding, a context-isolated preload\nbridge, Biome, path aliases, and multi-OS GitHub release builds — follow the\npatterns popularised by\n[`daltonmenezes/electron-app`](https://github.com/daltonmenezes/electron-app).\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomercnet%2Fyawab","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fomercnet%2Fyawab","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomercnet%2Fyawab/lists"}