diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7aad93d5..e995b992 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,6 +9,11 @@ on: pull_request: branches: [ main develop ] +# Required to push images to GHCR +permissions: + contents: read + packages: write + jobs: test: runs-on: ubuntu-latest @@ -35,7 +40,7 @@ jobs: uses: docker/metadata-action@v5 with: images: | - ${{ github.repository }} + ghcr.io/${{ github.repository }} tags: | type=schedule type=ref,event=branch @@ -48,12 +53,13 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@v3 - - name: Login to Docker Hub + - name: Login to GHCR if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -67,9 +73,3 @@ jobs: push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - - - name: Docker Hub Description - uses: peter-evans/dockerhub-description@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_PASSWORD }} \ No newline at end of file diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml new file mode 100644 index 00000000..c8b247e8 --- /dev/null +++ b/.github/workflows/sync-upstream.yml @@ -0,0 +1,76 @@ +name: Sync Upstream + Test + +on: + workflow_dispatch: + inputs: + upstream_repo: + description: "Upstream repo (owner/name)" + required: true + default: "clairton/unoapi-cloud" + tag: + description: "Tag to merge (e.g., v2.5.0-alpha-33)" + required: true + base_branch: + description: "Base branch to merge into" + required: false + default: "fixStatusBroadcast" + +jobs: + sync: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout base branch + uses: actions/checkout@v4 + with: + ref: ${{ inputs.base_branch }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' + cache: yarn + + - name: Configure Git + run: | + git config user.email "actions@github.com" + git config user.name "GitHub Actions" + + - name: Add upstream and fetch tags + run: | + git remote add upstream https://github.com/${{ inputs.upstream_repo }}.git + git fetch upstream --tags --prune + + - name: Create merge branch and merge tag + run: | + BRANCH="merge/${{ inputs.tag }}" + git checkout -b "$BRANCH" + git merge --no-ff -m "merge: upstream ${{ inputs.tag }}" "tags/${{ inputs.tag }}" + + - name: Install Yarn (classic) and dependencies + run: | + npm i -g yarn@1.22.22 + yarn install + + - name: Build + run: yarn build + + - name: Test + run: yarn test --coverage + + - name: Push branch + run: | + git push -u origin "merge/${{ inputs.tag }}" + + - name: Open Pull Request + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + base: ${{ inputs.base_branch }} + branch: merge/${{ inputs.tag }} + title: "Merge upstream ${{ inputs.tag }} into ${{ inputs.base_branch }}" + body: | + Automated upstream merge from ${{ inputs.upstream_repo }} tag ${{ inputs.tag }}. + CI built and tested this branch before opening the PR. diff --git a/Dockerfile b/Dockerfile index d13c58b7..119c6431 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ WORKDIR /app ADD ./package.json ./package.json ADD ./yarn.lock ./yarn.lock +ADD ./vendor ./vendor RUN yarn ADD ./src ./src @@ -35,6 +36,7 @@ COPY --from=builder /app/dist ./dist COPY --from=builder /app/public ./public COPY --from=builder /app/package.json ./package.json COPY --from=builder /app/yarn.lock ./yarn.lock +COPY --from=builder /app/vendor ./vendor RUN apk --update --no-cache add git ffmpeg diff --git a/README.md b/README.md index 501f0cc1..8a606ffb 100644 --- a/README.md +++ b/README.md @@ -52,8 +52,145 @@ http://localhost:9876/v15.0/554931978550/messages \ } }' ``` +To Send a Status +Requisitos: +to = 'status@broadcast' +type is content (ex.: image, video ou text) +statusJidList = ['5511999999999, ...] with at least 1 JID valid +Image sample +```sh +curl -i -X POST \ +http://localhost:9876/v15.0/5549988290955/messages \ +--header 'Content-Type: application/json' \ +--header 'Authorization: ••••••' \ +--data-raw ' +{ +"messaging_product": +"whatsapp", +"recipient_type": +"individual", +"to": +"status@broadcast", +"context": +{ +"message_id": +"8e401d25-89e8-4b9d-aa10-373e2ee1a5555" +}, +"type": +"image", +"image": +{ +"link": +"https://r2.vipertec.net/6YLUsdJFWE1SRdTcuGpi.png", +"caption": +"wificam" +}, +"statusJidList": +[ +"5566996269251", +"5566997195718", +"5566996222471" +] +} +``` +Video sample +```sh +curl -i -X POST \ +http://localhost:9876/v15.0/5549988290955/messages \ +--header 'Content-Type: application/json' \ +--header 'Authorization: ••••••' \ +--data-raw ' +{ +"messaging_product": +"whatsapp", +"recipient_type": +"individual", +"to": +"status@broadcast", +"context": +{ +"message_id": +"8e401d25-89e8-4b9d-aa10-373e2ee1a5555" +}, +"type": +"video", +"video": +{ +"link": +"https://r2.vipertec.net/fyHiN0XTnKtnDXbUwX9A.mp4", +"caption": +"AutoMonitoramento" +}, +"statusJidList": +[ +"5566996269251", +"5566997195718", +"5566996222471" +] +} +``` +Text sample +```sh +curl -i -X POST \ +http://localhost:9876/v15.0/5549988290955/messages \ +--header 'Content-Type: application/json' \ +--header 'Authorization: ••••••' \ +--data-raw ' +{ +"messaging_product": +"whatsapp", +"recipient_type": +"individual", +"to": +"status@broadcast", +"context": +{ +"message_id": +"8e401d25-89e8-4b9d-aa10-373e2ee1a5555" +}, +"type": "text", + "text": { + "body": "hello" + }, +"statusJidList": +[ +"5566996269251", +"5566997195718", +"5566996222471" +], +"backgroundColor": +"#000000", +"font": +1 +} +``` +Note: +Your number's WhatsApp status privacy must allow delivery to the provided JIDs. +If you provide an empty JIDList, the status will not be delivered. To send a contact +![Imagem do WhatsApp de 2025-09-21 à(s) 20 03 33_a199430a](https://github.com/user-attachments/assets/c498de41-b8dc-4368-98b0-737b2fee4735) + +New endpoint Preflight + +How to use for diagnostics + +Call preflight and check: +session.online = true (session connected) +counts.valid == counts.normalized (all counts exist in WhatsApp) +ready = true (prerequisites met) +If "ready" is false: +session.online = false → reconnect the session. +There are invalid numbers → the number doesn't have WhatsApp (correct or remove from the list). +Even with ready=true, Status may not appear due to privacy/saved contacts (not something the API can confirm). + +```sh +curl --location 'http://localhost:9876/v15.0/v15.0/5566996222471/preflight/status' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: ••••••' \ +--data '{ "statusJidList": ["5566996269251","5566997195718","5566996222471"] } +' +``` ```sh curl -i -X POST \ @@ -703,4 +840,4 @@ Mail to sales@unoapi.cloud - Connect with pairing code: https://github.com/WhiskeySockets/Baileys#starting-socket-with-pairing-code - Counting connection retry attempts even when restarting to prevent looping messages - Message delete endpoint -- Send reply message with please to send again, when any error and message enqueue in .dead \ No newline at end of file +- Send reply message with please to send again, when any error and message enqueue in .dead diff --git a/__tests__/services/client_baileys.ts b/__tests__/services/client_baileys.ts index f6671f3e..da280ddd 100644 --- a/__tests__/services/client_baileys.ts +++ b/__tests__/services/client_baileys.ts @@ -18,7 +18,7 @@ import { logout, } from '../../src/services/socket' import { mock, mockFn } from 'jest-mock-extended' -import { proto } from 'baileys' +import { proto } from '@whiskeysockets/baileys' import { DataStore } from '../../src/services/data_store' import { Incoming } from '../../src/services/incoming' import { dataStores } from '../../src/services/data_store' diff --git a/__tests__/services/message_filter.ts b/__tests__/services/message_filter.ts index d3576e4b..3eeaca27 100644 --- a/__tests__/services/message_filter.ts +++ b/__tests__/services/message_filter.ts @@ -1,4 +1,4 @@ -import { WAMessageKey } from 'baileys' +import { WAMessageKey } from '@whiskeysockets/baileys' import { Config, defaultConfig } from '../../src/services/config' import { MessageFilter } from '../../src/services/message_filter' diff --git a/__tests__/services/socket.ts b/__tests__/services/socket.ts index dd93984b..c6c0c3c2 100644 --- a/__tests__/services/socket.ts +++ b/__tests__/services/socket.ts @@ -1,6 +1,25 @@ -jest.mock('baileys') +jest.mock('@whiskeysockets/baileys', () => { + const fn = jest.fn() + return { + __esModule: true, + default: fn, + makeWASocket: fn, + Browsers: { ubuntu: (_: string) => ['Unoapi', 'Chrome', 'Linux'] }, + fetchLatestBaileysVersion: jest.fn(async () => ({ version: [2, 2, 2] })), + DisconnectReason: { loggedOut: 401, connectionReplaced: 440, restartRequired: 515, badSession: 500 }, + delay: jest.fn(async () => {}), + proto: {}, + } +}) +jest.mock('@whiskeysockets/baileys/lib/Utils/logger', () => { + const mockLogger = { + level: 'info', + child: () => ({ level: 'info' }), + } + return { __esModule: true, default: mockLogger } +}) import { OnDisconnected, OnQrCode, OnReconnect, OnNotification, connect } from '../../src/services/socket' -import makeWASocket, { WASocket, WAVersion } from 'baileys' +import makeWASocket, { WASocket, WAVersion } from '@whiskeysockets/baileys' import { mock } from 'jest-mock-extended' import { Store } from '../../src/services/store' import { defaultConfig } from '../../src/services/config' diff --git a/__tests__/services/transformer.ts b/__tests__/services/transformer.ts index 3eb82861..c8948687 100644 --- a/__tests__/services/transformer.ts +++ b/__tests__/services/transformer.ts @@ -1,4 +1,4 @@ -import { WAMessage, proto } from 'baileys' +import { WAMessage, proto } from '@whiskeysockets/baileys' import { phoneNumberToJid, getMessageType, diff --git a/jest.config.js b/jest.config.js index b413e106..27936293 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,4 +2,8 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', -}; \ No newline at end of file + moduleNameMapper: { + '^@whiskeysockets/baileys$': '/test-setup/baileys.mock.ts', + '^@whiskeysockets/baileys/lib/Utils/logger$': '/test-setup/baileys-logger.mock.ts', + }, +}; diff --git a/package.json b/package.json index d9b4cbf8..b7f68c22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "unoapi-cloud", - "version": "2.5.0-alpha-33", + "version": "3.0.0-beta-1", "description": "Unoapi Cloud", "exports": "./dist/index.js", "types": "./dist/index.d.ts", @@ -82,13 +82,13 @@ "@redis/client": "^1.5.14", "@sentry/node": "^9.29.0", "amqplib": "^0.10.8", + "axios": "^1.7.7", "audio2textjs": "^1.0.5", "awesome-phonenumber": "^6.8.0", - "baileys": "npm:whaileys@6.1.5", + "@whiskeysockets/baileys": "^7.0.0-rc.3", "dotenv": "^16.4.5", "express": "^4.19.2", "i18n": "^0.15.1", - "jimp": "^0.22.12", "jschardet": "^3.1.2", "link-preview-js": "^3.0.5", "mime-types": "^2.1.35", @@ -101,14 +101,19 @@ "qrcode-terminal": "^0.12.0", "sharp": "^0.34.1", "socket.io": "^4.7.5", + "socket.io-client": "^4.7.5", "socks-proxy-agent": "^8.0.3", "uuid": "^9.0.1", "vcf": "^2.1.2", - "voice-calls-baileys": "^1.0.5", "xlsx": "^0.18.5", - "yaml": "^2.7.0" + "yaml": "^2.7.0", + "voice-calls-baileys": "file:vendor/voice-calls-baileys" }, "engines": { "node": ">= 24.x" + }, + "resolutions": { + "libsignal": "https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/e81ecfc32eb74951d789ab37f7e341ab66d5fff1", + "@whiskeysockets/libsignal-node": "https://codeload.github.com/whiskeysockets/libsignal-node/tar.gz/e81ecfc32eb74951d789ab37f7e341ab66d5fff1" } } diff --git a/public/index.html b/public/index.html index 67a15855..09fb21ff 100644 --- a/public/index.html +++ b/public/index.html @@ -12,7 +12,13 @@ - + +