Skip to content

Commit 0122570

Browse files
committed
feat: CallMeBot external integration (WhatsApp & Signal notifications)
Port of the Gladys core callmebot service to an external integration built on @gladysassistant/integration-sdk, from the official JS template. Send-only communication channel: per-user credentials (messaging service, phone number, API key) declared in the manifest contact_schema and delivered through the CallMeBot HTTP API on onSendMessage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNSsmqpNe4BGMp59AmkvJr
0 parents  commit 0122570

20 files changed

Lines changed: 2000 additions & 0 deletions

.dockerignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
node_modules
2+
npm-debug.log*
3+
.git
4+
.github
5+
.env
6+
.env.*
7+
data
8+
*.log
9+
.DS_Store
10+
README.md

.github/workflows/build.yml

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# -----------------------------------------------------------------------------
2+
# Build & publish the multi-architecture image.
3+
#
4+
# Triggers:
5+
# - a Git version tag (e.g. `v1.2.0`) pushed by hand -> release build;
6+
# - a manual run (workflow_dispatch) on any branch -> test build;
7+
# - called by the Release workflow (workflow_call) -> release build for the
8+
# version it just bumped and tagged.
9+
# Result: an image pushed to the GitHub Container Registry (ghcr.io), for
10+
# linux/amd64 AND linux/arm64 (Raspberry Pi, etc.).
11+
#
12+
# Manual runs tag the image with the branch name (or the `image-tag` input)
13+
# and never touch `latest`. Release builds tag `:<version>` AND `:latest`.
14+
#
15+
# That is ALL it takes to "publish": tag, push, and the decentralized indexer
16+
# does the rest (no review, no account to create).
17+
# -----------------------------------------------------------------------------
18+
name: Build and publish image
19+
20+
on:
21+
push:
22+
tags:
23+
- 'v*'
24+
workflow_dispatch:
25+
inputs:
26+
image-tag:
27+
description: 'Image tag to publish (defaults to the branch name)'
28+
required: false
29+
default: ''
30+
# Called by release.yml after it bumps the version and pushes the tag.
31+
# A tag pushed with the default GITHUB_TOKEN does NOT re-trigger the
32+
# `push: tags` build above, so the Release workflow calls this one directly.
33+
workflow_call:
34+
inputs:
35+
version:
36+
description: 'Released version, e.g. 1.2.0 (publishes :<version> + :latest)'
37+
required: true
38+
type: string
39+
40+
jobs:
41+
build:
42+
runs-on: ubuntu-latest
43+
permissions:
44+
contents: read
45+
packages: write
46+
steps:
47+
- name: Checkout
48+
uses: actions/checkout@v4
49+
with:
50+
# Release build (called by release.yml): check out the tag it just
51+
# created, otherwise the default checkout would be the pre-bump
52+
# commit and the image would bundle a stale manifest. Hand-pushed
53+
# tags and manual runs keep the default checkout.
54+
ref: ${{ inputs.version != '' && format('v{0}', inputs.version) || '' }}
55+
56+
- name: Set up QEMU
57+
uses: docker/setup-qemu-action@v3
58+
59+
- name: Set up Docker Buildx
60+
uses: docker/setup-buildx-action@v3
61+
62+
- name: Log in to GitHub Container Registry
63+
uses: docker/login-action@v3
64+
with:
65+
registry: ghcr.io
66+
username: ${{ github.actor }}
67+
password: ${{ secrets.GITHUB_TOKEN }}
68+
69+
- name: Derive image tags
70+
id: meta
71+
env:
72+
INPUT_IMAGE_TAG: ${{ inputs.image-tag }}
73+
INPUT_VERSION: ${{ inputs.version }}
74+
run: |
75+
image="ghcr.io/${GITHUB_REPOSITORY,,}"
76+
# Detect a release build by the `version` input, NOT the event name:
77+
# inside a called (workflow_call) workflow, GITHUB_EVENT_NAME is the
78+
# caller's event ("workflow_dispatch"), so it cannot tell the two
79+
# apart. The input is only set when release.yml calls us.
80+
if [ -n "$INPUT_VERSION" ]; then
81+
# Release build driven by release.yml: it already bumped and tagged
82+
# this exact version. Publish `:<version>` AND move `latest`.
83+
version="${INPUT_VERSION}"
84+
tags="${image}:${version},${image}:latest"
85+
elif [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
86+
# Manual test build: use the input tag, or the branch name
87+
# (slashes replaced by dashes). Never move `latest`.
88+
version="${INPUT_IMAGE_TAG:-${GITHUB_REF_NAME//\//-}}"
89+
if [ "$version" = "latest" ]; then
90+
echo "::error::Cannot use 'latest' as a manual image tag."
91+
exit 1
92+
fi
93+
tags="${image}:${version}"
94+
else
95+
# Hand-pushed tag `v1.2.0` -> 1.2.0
96+
version="${GITHUB_REF_NAME#v}"
97+
tags="${image}:${version},${image}:latest"
98+
fi
99+
echo "image_ref=${image}:${version}" >> "$GITHUB_OUTPUT"
100+
echo "tags=$tags" >> "$GITHUB_OUTPUT"
101+
102+
- name: Build and push (amd64 + arm64)
103+
uses: docker/build-push-action@v6
104+
with:
105+
context: .
106+
platforms: linux/amd64,linux/arm64
107+
push: true
108+
tags: ${{ steps.meta.outputs.tags }}
109+
110+
- name: Docker build summary
111+
env:
112+
IMAGE_REF: ${{ steps.meta.outputs.image_ref }}
113+
run: |
114+
# Manifest with `docker_image` pointing at the image just pushed,
115+
# ready to paste into Gladys developer mode.
116+
manifest=$(jq --arg image "$IMAGE_REF" '.docker_image = $image' \
117+
gladys-assistant-integration.json)
118+
{
119+
echo "## Docker build summary"
120+
echo ""
121+
echo "Image pushed (linux/amd64 + linux/arm64):"
122+
echo ""
123+
echo '```'
124+
echo "$IMAGE_REF"
125+
echo '```'
126+
echo ""
127+
echo "### Test this build in Gladys"
128+
echo ""
129+
echo "1. In Gladys, open **Integrations → Install an integration → Developer mode**."
130+
echo "2. Copy the manifest below (its \`docker_image\` already points to this build):"
131+
echo ""
132+
echo '```json'
133+
echo "$manifest"
134+
echo '```'
135+
} >> "$GITHUB_STEP_SUMMARY"

.github/workflows/ci.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# -----------------------------------------------------------------------------
2+
# Continuous integration: runs on every push and every pull request.
3+
#
4+
# Three gates, the same ones every developer inspired by this template should
5+
# keep:
6+
# 1. Prettier -> consistent formatting (no style debates in reviews);
7+
# 2. ESLint -> catch real mistakes (undefined vars, dead code, ...);
8+
# 3. Tests -> `node --test`, the built-in runner (no extra dependency).
9+
#
10+
# Runs on Node 24, the version the Docker image ships with (see the Dockerfile),
11+
# so the check exercises the exact runtime the integration runs on.
12+
# -----------------------------------------------------------------------------
13+
name: CI
14+
15+
on:
16+
push:
17+
branches:
18+
- main
19+
pull_request:
20+
21+
jobs:
22+
quality:
23+
runs-on: ubuntu-latest
24+
name: Lint & test
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v4
28+
29+
- name: Set up Node.js
30+
uses: actions/setup-node@v4
31+
with:
32+
node-version: 24
33+
cache: npm
34+
35+
- name: Install dependencies
36+
run: npm ci
37+
38+
- name: Check formatting (Prettier)
39+
run: npm run format:check
40+
41+
- name: Lint (ESLint)
42+
run: npm run lint
43+
44+
- name: Run tests
45+
run: npm test

.github/workflows/release.yml

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# -----------------------------------------------------------------------------
2+
# Cut a new release — entirely from the GitHub UI, no local tooling needed.
3+
#
4+
# In GitHub: Actions -> Release -> "Run workflow" -> pick patch / minor / major.
5+
#
6+
# What it does, in one run:
7+
# 1. computes the next semver from `package.json` (npm version rules);
8+
# 2. writes it into `package.json`, `package-lock.json` AND the manifest
9+
# (`gladys-assistant-integration.json`: both `version` and the
10+
# `docker_image` tag, so the indexer and the image stay in lockstep);
11+
# 3. commits the bump and pushes the `vX.Y.Z` tag;
12+
# 4. calls build.yml to publish the multi-arch image (`:X.Y.Z` + `:latest`).
13+
#
14+
# Why call build.yml instead of relying on the tag push: a tag pushed with the
15+
# default GITHUB_TOKEN does NOT trigger other workflows, so we invoke the build
16+
# ourselves. No personal access token to create.
17+
# -----------------------------------------------------------------------------
18+
name: Release
19+
20+
on:
21+
workflow_dispatch:
22+
inputs:
23+
release-type:
24+
description: 'Version bump for this release'
25+
required: true
26+
type: choice
27+
default: patch
28+
options:
29+
- patch
30+
- minor
31+
- major
32+
33+
jobs:
34+
prepare:
35+
runs-on: ubuntu-latest
36+
permissions:
37+
contents: write # commit the version bump and push the tag
38+
outputs:
39+
version: ${{ steps.bump.outputs.version }}
40+
steps:
41+
- name: Checkout
42+
uses: actions/checkout@v4
43+
with:
44+
fetch-depth: 0
45+
46+
- name: Set up Node.js
47+
uses: actions/setup-node@v4
48+
with:
49+
node-version: 24
50+
51+
- name: Bump version and update the manifest
52+
id: bump
53+
env:
54+
RELEASE_TYPE: ${{ inputs.release-type }}
55+
run: |
56+
set -euo pipefail
57+
58+
# npm computes the next semver and writes it to package.json (and
59+
# package-lock.json). --no-git-tag-version: we own the commit + tag
60+
# below, so npm must not touch git.
61+
new_version="$(npm version "$RELEASE_TYPE" --no-git-tag-version | tail -n1)"
62+
new_version="${new_version#v}" # v1.2.0 -> 1.2.0
63+
64+
image="ghcr.io/${GITHUB_REPOSITORY,,}:${new_version}"
65+
66+
# Keep the manifest in lockstep: the version the indexer reads AND the
67+
# image tag Gladys pulls must both point at this release.
68+
tmp="$(mktemp)"
69+
jq --arg version "$new_version" --arg image "$image" \
70+
'.version = $version | .docker_image = $image' \
71+
gladys-assistant-integration.json > "$tmp"
72+
mv "$tmp" gladys-assistant-integration.json
73+
74+
echo "version=${new_version}" >> "$GITHUB_OUTPUT"
75+
76+
# Note: if the branch you release from is protected against direct pushes,
77+
# GitHub rejects the push below (the default GITHUB_TOKEN cannot bypass
78+
# branch protection). Allow GitHub Actions to bypass the rule, or release
79+
# from an unprotected branch.
80+
- name: Commit, tag and push
81+
env:
82+
VERSION: ${{ steps.bump.outputs.version }}
83+
run: |
84+
set -euo pipefail
85+
git config user.name "github-actions[bot]"
86+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
87+
88+
git add package.json package-lock.json gladys-assistant-integration.json
89+
git commit -m "chore(release): ${VERSION}"
90+
git tag -a "v${VERSION}" -m "Release ${VERSION}"
91+
92+
git push origin "HEAD:${GITHUB_REF_NAME}"
93+
git push origin "v${VERSION}"
94+
95+
# Build and publish the image for the version we just tagged.
96+
build:
97+
needs: prepare
98+
uses: ./.github/workflows/build.yml
99+
permissions:
100+
contents: read
101+
packages: write
102+
with:
103+
version: ${{ needs.prepare.outputs.version }}

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
node_modules/
2+
npm-debug.log*
3+
.env
4+
.env.*
5+
data/
6+
*.log
7+
.DS_Store

.prettierignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules/
2+
package-lock.json
3+
data/
4+
*.log

.prettierrc.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"printWidth": 100,
3+
"singleQuote": true,
4+
"trailingComma": "all",
5+
"semi": true,
6+
"tabWidth": 2,
7+
"arrowParens": "always"
8+
}

Dockerfile

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# -----------------------------------------------------------------------------
2+
# Integration image.
3+
#
4+
# Gladys sandbox constraints ("the sandbox is the defense"):
5+
# - rootfs mounted READ-ONLY -> never write outside /data
6+
# - a single writable volume: /data
7+
# - runs as a non-root user
8+
# - multi-arch image (linux/amd64 + linux/arm64), see the CI workflow
9+
# -----------------------------------------------------------------------------
10+
11+
FROM node:24-alpine
12+
13+
# dumb-init: handles signals (SIGTERM) correctly for a graceful shutdown.
14+
RUN apk add --no-cache dumb-init
15+
16+
WORKDIR /app
17+
18+
# Install the PROD dependencies first (better build cache).
19+
COPY package.json package-lock.json* ./
20+
RUN npm ci --omit=dev || npm install --omit=dev
21+
22+
# Then the integration code.
23+
COPY index.js ./
24+
COPY src ./src
25+
COPY gladys-assistant-integration.json ./
26+
27+
# The only writable location allowed at runtime.
28+
ENV NODE_ENV=production
29+
VOLUME ["/data"]
30+
31+
# Run as an unprivileged user (already present in the node image).
32+
USER node
33+
34+
ENTRYPOINT ["dumb-init", "--"]
35+
CMD ["node", "index.js"]

0 commit comments

Comments
 (0)