├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .npmrc ├── .github ├── FUNDING.yml ├── workflows │ ├── commitlint.yml │ ├── lint.yml │ ├── dependabot-auto-merge.yml │ ├── test.yml │ └── docker.yml └── renovate.json ├── .prettierignore ├── .prettierrc ├── .gitignore ├── .npmignore ├── .editorconfig ├── .release-it.yml ├── .dockerignore ├── .vscode ├── tasks.json └── launch.json ├── .commitlintrc.json ├── tsconfig.json ├── .env ├── docker-compose.yml ├── src ├── server.ts ├── controllers │ ├── index.controller.ts │ └── link-preview.controller.ts ├── exceptions │ └── HttpException.ts ├── utils │ ├── cache.ts │ ├── encodeToPng.ts │ └── logger.ts ├── middlewares │ └── error.middleware.ts ├── config │ └── index.ts └── app.ts ├── Dockerfile ├── .eslintrc.json ├── package.json ├── README.md └── LICENSE /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | issuehunt: wppconnect-team 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | docs-source 3 | html 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.local 2 | *.tgz 3 | /.idea 4 | /debug.log 5 | /dist/ 6 | /logs 7 | /node_modules 8 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install pretty-quick --staged 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.tgz 3 | *.zip 4 | /docs 5 | /docs-source 6 | /src 7 | /tsconfig.json 8 | /typedoc.json 9 | /userDataDir 10 | /wa-source 11 | /webpack.config.js 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | 8 | [{package.json}] 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.release-it.yml: -------------------------------------------------------------------------------- 1 | git: 2 | commitMessage: 'chore(release): v${version}' 3 | tagAnnotation: 'chore(release): v${version}' 4 | tagName: 'v${version}' 5 | 6 | hooks: 7 | after:bump: 8 | - 'npm run changelog:update' 9 | 10 | # automatic publish from github workflow 11 | npm: 12 | publish: false 13 | private: true 14 | registry: 'OMITTED' 15 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # ignore .git and .cache folders 2 | .cache* 3 | 4 | # ignore all *.class files in all folders, including build root 5 | **/*.class 6 | 7 | # ignore all markdown files (md) beside all README*.md other than README-secret.md 8 | *.md 9 | !README*.md 10 | README-secret.md 11 | 12 | # ignore 13 | *Dockerfile* 14 | *docker-compose* 15 | node_modules 16 | .env* -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "watch", 7 | "group": { 8 | "kind": "build", 9 | "isDefault": true 10 | }, 11 | "isBackground": true, 12 | "problemMatcher": [ 13 | "$tsc-watch" 14 | ], 15 | "label": "npm: watch", 16 | "detail": "tsc watch", 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/commitlintrc.json", 3 | "extends": ["@commitlint/config-conventional"], 4 | "rules": { 5 | "body-max-line-length": [0, "always", 100], 6 | "header-max-length": [2, "always", 120], 7 | "subject-case": [ 8 | 1, 9 | "never", 10 | ["sentence-case", "start-case", "pascal-case", "upper-case"] 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "outDir": "./dist", 10 | "rootDir": "./src", 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "sourceMap": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/commitlint.yml: -------------------------------------------------------------------------------- 1 | name: commit lint 2 | on: [pull_request] 3 | 4 | jobs: 5 | commitlint: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v3 10 | with: 11 | fetch-depth: 0 12 | 13 | - name: Lint commit 14 | uses: wagoid/commitlint-github-action@v5.0.2 15 | with: 16 | configFile: './.commitlintrc.js' 17 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | ### PORT 2 | #PORT = 8000 3 | 4 | ### TRUST PROXY (number or IP) 5 | #TRUST_PROXY = 1 6 | #TRUST_PROXY = 192.168.0.1 7 | 8 | ### LOG 9 | #LOG_FORMAT = combined 10 | #LOG_DIR = ./logs 11 | 12 | ### CORS 13 | #ORIGIN = "https://web.whatsapp.com" 14 | 15 | ### CACHE 16 | # CACHE_MAX_ITEMS = 500 17 | # CACHE_MAX_SIZE = 104857600 # 100MB 18 | # CACHE_TTL = 60 * 60 * 1000 # 1 hour 19 | 20 | ### USER AGENT 21 | #USER_AGENT = "WhatsApp/2.2214.12 N" 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use o IntelliSense para saber mais sobre os atributos possíveis. 3 | // Focalizar para exibir as descrições dos atributos existentes. 4 | // Para obter mais informações, acesse: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Server", 11 | "presentation": { 12 | "hidden": false, 13 | "group": "Other", 14 | "order": 2 15 | }, 16 | "preLaunchTask": "npm: watch", 17 | // "runtimeExecutable": "npm", 18 | "runtimeArgs": [ 19 | "./dist/server.js", 20 | ], 21 | "cwd": "${workspaceFolder}", 22 | "console": "integratedTerminal", 23 | } 24 | ], 25 | } 26 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | services: 3 | backend: 4 | container_name: WPPconnectLinkPreview 5 | hostname: WPPconnectLinkPreview 6 | network_mode: bridge 7 | image: wppconnect/wa-js-api-server:1.0.0 8 | build: 9 | context: . 10 | dockerfile: Dockerfile 11 | restart: always 12 | environment: 13 | NODE_ENV: production 14 | PORT: 8000 15 | LOG_FORMAT: ${LOG_FORMAT:-'combined'} 16 | LOG_DIR: ${LOG_DIR:-'./logs'} 17 | ORIGIN: ${ORIGIN:-'https://web.whatsapp.com'} 18 | CACHE_MAX_ITEMS: ${CACHE_MAX_ITEMS:-500} 19 | CACHE_MAX_SIZE: ${CACHE_MAX_SIZE:-104857600} 20 | CACHE_TTL: ${CACHE_TTL:-3600000} 21 | TRUST_PROXY: ${TRUST_PROXY:-1} 22 | USER_AGENT: ${TRUST_PROXY:-'WhatsApp/2.2214.12 N'} 23 | ports: 24 | - ${PORT:-8000}:8000 25 | # 26 | # docker-compose up -d 27 | # docker-compose -f docker-compose.yml up --build -d 28 | # 29 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2021 WPPConnect Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import App from './app'; 18 | import { IndexController } from './controllers/index.controller'; 19 | import { LinkPreviewController } from './controllers/link-preview.controller'; 20 | 21 | const app = new App([IndexController, LinkPreviewController]); 22 | app.listen(); 23 | -------------------------------------------------------------------------------- /src/controllers/index.controller.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2021 WPPConnect Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Controller, Get } from 'routing-controllers'; 18 | 19 | @Controller() 20 | export class IndexController { 21 | @Get('/') 22 | index() { 23 | return 'OK'; 24 | } 25 | 26 | @Get('/status') 27 | statis() { 28 | return 'OK'; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | 18 | - name: Setup Node 19 | uses: actions/setup-node@v3.4.1 20 | with: 21 | node-version: 16.x 22 | 23 | - name: Get npm cache directory 24 | id: npm-cache 25 | run: | 26 | echo "::set-output name=dir::$(npm config get cache)" 27 | - name: Setup npm cache 28 | uses: actions/cache@v3 29 | with: 30 | path: ${{ steps.npm-cache.outputs.dir }} 31 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 32 | restore-keys: | 33 | ${{ runner.os }}-node- 34 | 35 | - name: Install Dependencies 36 | run: npm ci || npm install 37 | 38 | - name: Lint source 39 | run: npm run lint 40 | -------------------------------------------------------------------------------- /src/exceptions/HttpException.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2021 WPPConnect Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { HttpError } from 'routing-controllers'; 18 | 19 | export class HttpException extends HttpError { 20 | public status: number; 21 | public message: string; 22 | 23 | constructor(status: number, message: string) { 24 | super(status, message); 25 | this.status = status; 26 | this.message = message; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/cache.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2021 WPPConnect Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import LRUCache from 'lru-cache'; 18 | 19 | import { CACHE_MAX_ITEMS, CACHE_MAX_SIZE, CACHE_TTL } from '../config'; 20 | 21 | export const cache = new LRUCache({ 22 | max: CACHE_MAX_ITEMS, 23 | maxSize: CACHE_MAX_SIZE, 24 | ttl: CACHE_TTL, 25 | sizeCalculation: (value) => { 26 | if (Buffer.isBuffer(value) || typeof value === 'string') { 27 | return value.length; 28 | } 29 | return JSON.stringify(value).length; 30 | }, 31 | allowStale: false, 32 | updateAgeOnGet: false, 33 | }); 34 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | 3 | on: pull_request_target 4 | 5 | permissions: 6 | pull-requests: write 7 | contents: write 8 | 9 | jobs: 10 | dependabot: 11 | runs-on: ubuntu-latest 12 | if: ${{ github.actor == 'dependabot[bot]' }} 13 | steps: 14 | - name: Wait for tests to succeed 15 | uses: lewagon/wait-on-check-action@v1.1.2 16 | with: 17 | ref: ${{ github.event.pull_request.head.sha }} 18 | repo-token: ${{ secrets.GITHUB_TOKEN }} 19 | running-workflow-name: dependabot 20 | wait-interval: 15 21 | 22 | - name: Dependabot metadata 23 | id: metadata 24 | uses: dependabot/fetch-metadata@v1.3.6 25 | with: 26 | github-token: '${{ secrets.GITHUB_TOKEN }}' 27 | 28 | - name: Enable auto-merge for Dependabot PRs 29 | if: >- 30 | ${{steps.metadata.outputs.update-type == 'version-update:semver-patch' 31 | || contains(steps.metadata.outputs.dependency-names, '@types')}} 32 | || contains(steps.metadata.outputs.dependency-names, 'commitlint')}} 33 | || contains(steps.metadata.outputs.dependency-names, 'eslint')}} 34 | run: gh pr merge --squash "$PR_URL" 35 | env: 36 | PR_URL: ${{github.event.pull_request.html_url}} 37 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 38 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | 18 | - name: Setup Node 19 | uses: actions/setup-node@v3.4.1 20 | with: 21 | node-version: 16.x 22 | 23 | - name: Get npm cache directory 24 | id: npm-cache 25 | run: | 26 | echo "::set-output name=dir::$(npm config get cache)" 27 | - name: Setup npm cache 28 | uses: actions/cache@v3 29 | with: 30 | path: ${{ steps.npm-cache.outputs.dir }} 31 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 32 | restore-keys: | 33 | ${{ runner.os }}-node- 34 | 35 | - name: Install Dependencies 36 | run: npm ci || npm install 37 | 38 | - name: Build source 39 | run: npm run build 40 | 41 | - name: Run the API test 42 | run: | 43 | npm run start & 44 | sleep 5 45 | curl --fail "http://localhost:8000/v1/link-preview/fetch-data.png?url=https://www.youtube.com" --output /dev/null 46 | curl --fail "http://localhost:8000/v1/link-preview/fetch-data.json?url=https://www.youtube.com" 47 | curl --fail "http://localhost:8000/v1/link-preview/download-image?url=https://www.youtube.com/img/desktop/yt_1200.png" --output /dev/null 48 | kill -9 `lsof -i:8000 -t` 49 | -------------------------------------------------------------------------------- /src/middlewares/error.middleware.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2021 WPPConnect Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { NextFunction, Request, Response } from 'express'; 18 | 19 | import { HttpException } from '../exceptions/HttpException'; 20 | import { logger } from '../utils/logger'; 21 | 22 | const errorMiddleware = ( 23 | error: HttpException, 24 | req: Request, 25 | res: Response, 26 | next: NextFunction 27 | ) => { 28 | try { 29 | const status: number = error.status || 500; 30 | const message: string = error.message || 'Something went wrong'; 31 | 32 | console.error( 33 | `[${req.method}] ${req.path} >> StatusCode:: ${status}, Message:: ${message}` 34 | ); 35 | logger.error( 36 | `[${req.method}] ${req.path} >> StatusCode:: ${status}, Message:: ${message}` 37 | ); 38 | res.status(status).json({ status, message, error: true }); 39 | } catch (error) { 40 | next(error); 41 | } 42 | }; 43 | 44 | export default errorMiddleware; 45 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base", ":prHourlyLimitNone", ":semanticCommits"], 4 | "prConcurrentLimit": 3, 5 | "lockFileMaintenance": { 6 | "enabled": true, 7 | "automerge": true 8 | }, 9 | "packageRules": [ 10 | { 11 | "description": "Use bump strategy", 12 | "matchPackagePatterns": ["*"], 13 | "rangeStrategy": "bump", 14 | "semanticCommitType": "build", 15 | "labels": ["dependencies"] 16 | }, 17 | { 18 | "matchManagers": ["github-actions"], 19 | "semanticCommitType": "ci", 20 | "addLabels": ["github_actions"] 21 | }, 22 | { 23 | "matchManagers": ["npm"], 24 | "addLabels": ["javascript"] 25 | }, 26 | { 27 | "matchManagers": ["dockerfile"], 28 | "addLabels": ["docker"] 29 | }, 30 | { 31 | "matchDepTypes": ["devDependencies"], 32 | "semanticCommitScope": "deps-dev" 33 | }, 34 | { 35 | "description": "Automatically merge minor and patch-level updates", 36 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 37 | "automerge": true, 38 | "automergeStrategy": "squash", 39 | "automergeType": "pr" 40 | }, 41 | { 42 | "description": "Ignore major version", 43 | "matchPackageNames": ["@types/node"], 44 | "allowedVersions": "<17" 45 | }, 46 | { 47 | "description": "Ignore major version", 48 | "matchManagers": ["dockerfile"], 49 | "matchPackageNames": ["node"], 50 | "allowedVersions": "<17" 51 | }, 52 | { 53 | "description": "Ignore major version", 54 | "matchPackageNames": ["node-fetch"], 55 | "allowedVersions": "<3" 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: docker 2 | 3 | on: 4 | schedule: 5 | - cron: '0 10 * * *' # everyday at 10am 6 | push: 7 | branches: 8 | - 'main' 9 | tags: 10 | - 'v*' 11 | 12 | jobs: 13 | docker: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | 19 | - name: Docker meta 20 | id: meta 21 | uses: docker/metadata-action@v4 22 | with: 23 | # list of Docker images to use as base name for tags 24 | images: | 25 | wppconnect/wa-js-api-server 26 | # generate Docker tags based on the following events/attributes 27 | tags: | 28 | type=schedule,pattern=nightly 29 | type=ref,event=branch 30 | type=ref,event=pr 31 | type=semver,pattern={{version}} 32 | type=semver,pattern={{major}}.{{minor}} 33 | type=semver,pattern={{major}} 34 | 35 | - name: Set up QEMU 36 | uses: docker/setup-qemu-action@v2 37 | 38 | - name: Set up Docker Buildx 39 | uses: docker/setup-buildx-action@v2 40 | 41 | - name: Login to DockerHub 42 | if: github.event_name != 'pull_request' 43 | uses: docker/login-action@v2 44 | with: 45 | username: ${{ secrets.DOCKERHUB_USERNAME }} 46 | password: ${{ secrets.DOCKERHUB_TOKEN }} 47 | 48 | - name: Build and push 49 | id: docker_build 50 | uses: docker/build-push-action@v3 51 | with: 52 | push: ${{ github.event_name != 'pull_request' }} 53 | tags: ${{ steps.meta.outputs.tags }} 54 | labels: ${{ steps.meta.outputs.labels }} 55 | 56 | - name: Image digest 57 | run: echo ${{ steps.docker_build.outputs.digest }} 58 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2021 WPPConnect Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { config } from 'dotenv'; 18 | import { expand } from 'dotenv-expand'; 19 | import { cleanEnv, num, port, str } from 'envalid'; 20 | 21 | expand(config({ path: `.env` })); 22 | expand(config({ path: `.env.local` })); 23 | expand(config({ path: `.env.${process.env.NODE_ENV || 'development'}` })); 24 | expand(config({ path: `.env.${process.env.NODE_ENV || 'development'}.local` })); 25 | expand(config()); 26 | 27 | const env = cleanEnv(process.env, { 28 | CACHE_MAX_ITEMS: num({ default: 5000 }), 29 | CACHE_MAX_SIZE: num({ default: 100 * 1024 * 1024 }), // 100MB 30 | CACHE_TTL: num({ default: 60 * 60 * 1000 }), // 1 hour 31 | LOG_DIR: str({ default: './logs' }), 32 | LOG_FORMAT: str({ default: 'combined' }), 33 | NODE_ENV: str({ default: 'development' }), 34 | ORIGIN: str({ default: 'https://web.whatsapp.com' }), 35 | PORT: port({ default: 8000 }), 36 | TRUST_PROXY: str({ default: '1' }), 37 | USER_AGENT: str({ default: 'WhatsApp/2.2214.12 N' }), 38 | }); 39 | 40 | export const { 41 | CACHE_MAX_ITEMS, 42 | CACHE_MAX_SIZE, 43 | CACHE_TTL, 44 | LOG_DIR, 45 | LOG_FORMAT, 46 | NODE_ENV, 47 | ORIGIN, 48 | PORT, 49 | TRUST_PROXY, 50 | USER_AGENT, 51 | } = env; 52 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ### base image 2 | # Create an intermediate image for build speed with production dependencies 3 | FROM node:16-alpine as base 4 | 5 | RUN mkdir -p /home/wa-js-api-server && \ 6 | mkdir -p /home/wa-js-api-server/logs 7 | 8 | WORKDIR /home/wa-js-api-server 9 | 10 | ENV NODE_ENV=production 11 | 12 | COPY .npmrc package.json package-lock.json LICENSE ./ 13 | 14 | RUN npm set-script prepare "" && \ 15 | npm install --production && \ 16 | npm cache clean --force 17 | 18 | 19 | ### build image 20 | # Create an image to only build the package and copy to final image 21 | FROM base as build 22 | 23 | WORKDIR /home/wa-js-api-server 24 | 25 | COPY .npmrc package.json package-lock.json ./ 26 | 27 | # install the devDependencies 28 | RUN npm set-script prepare "" && \ 29 | npm install --production=false 30 | 31 | COPY . . 32 | 33 | RUN npm run build 34 | 35 | 36 | ### final image 37 | FROM base 38 | 39 | LABEL version="1.0.0" description="WPPConnectLinkPreview" maintainer="Alan Martines" 40 | 41 | WORKDIR /home/wa-js-api-server 42 | 43 | COPY --from=build /home/wa-js-api-server/dist /home/wa-js-api-server/dist/ 44 | 45 | EXPOSE 8000/tcp 46 | 47 | CMD [ "node", "--trace-warnings", "dist/server.js" ] 48 | 49 | ## Acessar bash do container 50 | # docker exec -it /bin/sh 51 | # docker exec -it /bin/bash 52 | 53 | ## Logs do container 54 | # docker logs -f --tail 1000 WPPconnectLinkPreview 55 | 56 | ## Removendo todos os containers e imagens de uma só vez 57 | # docker rm $(docker ps -qa) 58 | 59 | ## Removendo todas as imagens de uma só vez 60 | # docker rmi $(docker images -aq) 61 | 62 | ## Removendo imagens 63 | # docker rmi 64 | # docker rmi 65 | 66 | ## Como obter o endereço IP de um contêiner Docker do host 67 | # https://stack.desenvolvedor.expert/appendix/docker/rede.html 68 | # docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 69 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint", "simple-import-sort", "import", "header"], 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:prettier/recommended" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/explicit-module-boundary-types": "off", 13 | "@typescript-eslint/no-explicit-any": "off", 14 | "@typescript-eslint/no-non-null-assertion": "off", 15 | "header/header": [ 16 | 2, 17 | "block", 18 | [ 19 | "!", 20 | { 21 | "pattern": "^ \\* Copyright \\d{4} WPPConnect Team$", 22 | "template": " * Copyright 2022 WPPConnect Team" 23 | }, 24 | " *", 25 | " * Licensed under the Apache License, Version 2.0 (the \"License\");", 26 | " * you may not use this file except in compliance with the License.", 27 | " * You may obtain a copy of the License at", 28 | " *", 29 | " * http://www.apache.org/licenses/LICENSE-2.0", 30 | " *", 31 | " * Unless required by applicable law or agreed to in writing, software", 32 | " * distributed under the License is distributed on an \"AS IS\" BASIS,", 33 | " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.", 34 | " * See the License for the specific language governing permissions and", 35 | " * limitations under the License.", 36 | " " 37 | ], 38 | 2 39 | ], 40 | "import/first": "error", 41 | "import/newline-after-import": "error", 42 | "import/no-duplicates": "error", 43 | "no-empty": ["error", { "allowEmptyCatch": true }], 44 | "simple-import-sort/exports": "error", 45 | "simple-import-sort/imports": "error" 46 | }, 47 | "ignorePatterns": ["webpack.config.js", "dist/*", "wa-source/*"], 48 | "overrides": [ 49 | { 50 | "files": ["src/whatsapp/**/*.ts"], 51 | "rules": { 52 | "@typescript-eslint/no-empty-interface": "off", 53 | "@typescript-eslint/no-namespace": "off" 54 | } 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /src/utils/encodeToPng.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2021 WPPConnect Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { toPng } from '@rgba-image/png'; 18 | 19 | export function encodeToPng(content: ArrayBufferView | string) { 20 | if (typeof content === 'string') { 21 | content = Buffer.from(content); 22 | } 23 | 24 | const arrayData = new Uint8Array( 25 | content.buffer, 26 | content.byteOffset, 27 | content.byteLength 28 | ); 29 | 30 | // Set image size 31 | const headerSize = 12; 32 | const length = arrayData.length; 33 | const width = Math.ceil(Math.sqrt((length + headerSize) / 3)); 34 | const height = width; 35 | 36 | // Create a image data 37 | const data = new Uint8ClampedArray(width * height * 4); 38 | 39 | // 1 byte for type or future use 40 | // 8 bytes for size; 41 | // first 3 pixels for length 42 | 43 | const bufSize = Buffer.alloc(8); 44 | bufSize.writeBigUInt64BE(BigInt(length)); 45 | 46 | data[0] = 0; //red 47 | data[1] = bufSize[0]; //green 48 | data[2] = bufSize[1]; //blue 49 | data[3] = 255; //alfa 50 | data[4] = bufSize[2]; //red 51 | data[5] = bufSize[3]; //green 52 | data[6] = bufSize[4]; //blue 53 | data[7] = 255; //alfa 54 | data[8] = bufSize[5]; //red 55 | data[9] = bufSize[6]; //green 56 | data[10] = bufSize[7]; //blue 57 | data[11] = 255; //alfa 58 | 59 | let i = 0; 60 | let o = headerSize; 61 | while (i < length) { 62 | data[o] = arrayData[i] || 0; //red 63 | data[o + 1] = arrayData[i + 1] || 0; //green 64 | data[o + 2] = arrayData[i + 2] || 0; //blue 65 | data[o + 3] = 255; //alfa 66 | 67 | i += 3; 68 | o += 4; 69 | } 70 | 71 | const encodedPng = toPng({ 72 | data, 73 | height, 74 | width, 75 | colorSpace: 'srgb', 76 | } as any); 77 | 78 | return encodedPng; 79 | } 80 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2021 WPPConnect Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { existsSync, mkdirSync } from 'fs'; 18 | import { resolve } from 'path'; 19 | import winston from 'winston'; 20 | import winstonDaily from 'winston-daily-rotate-file'; 21 | 22 | import { LOG_DIR } from '../config'; 23 | 24 | // logs dir 25 | const logDir: string = resolve(process.cwd(), LOG_DIR); 26 | 27 | if (!existsSync(logDir)) { 28 | mkdirSync(logDir); 29 | } 30 | 31 | // Define log format 32 | const logFormat = winston.format.printf( 33 | ({ timestamp, level, message }) => `${timestamp} ${level}: ${message}` 34 | ); 35 | 36 | /* 37 | * Log Level 38 | * error: 0, warn: 1, info: 2, http: 3, verbose: 4, debug: 5, silly: 6 39 | */ 40 | const logger = winston.createLogger({ 41 | format: winston.format.combine( 42 | winston.format.timestamp({ 43 | format: 'YYYY-MM-DD HH:mm:ss', 44 | }), 45 | logFormat 46 | ), 47 | transports: [ 48 | // debug log setting 49 | new winstonDaily({ 50 | level: 'debug', 51 | datePattern: 'YYYY-MM-DD', 52 | dirname: logDir + '/debug', // log file /logs/debug/*.log in save 53 | filename: `%DATE%.log`, 54 | maxFiles: 30, // 30 Days saved 55 | json: false, 56 | zippedArchive: true, 57 | }), 58 | // error log setting 59 | new winstonDaily({ 60 | level: 'error', 61 | datePattern: 'YYYY-MM-DD', 62 | dirname: logDir + '/error', // log file /logs/error/*.log in save 63 | filename: `%DATE%.log`, 64 | maxFiles: 30, // 30 Days saved 65 | handleExceptions: true, 66 | json: false, 67 | zippedArchive: true, 68 | }), 69 | ], 70 | }); 71 | 72 | logger.add( 73 | new winston.transports.Console({ 74 | format: winston.format.combine( 75 | winston.format.splat(), 76 | winston.format.colorize() 77 | ), 78 | }) 79 | ); 80 | 81 | const stream = { 82 | write: (message: string) => { 83 | logger.info(message.substring(0, message.lastIndexOf('\n'))); 84 | }, 85 | }; 86 | 87 | export { logger, stream }; 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wppconnect/wa-js-api-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "license": "Apache-2.0", 6 | "author": { 7 | "name": "Edgard Messias", 8 | "email": "edgardmessias@gmail.com", 9 | "url": "https://github.com/edgardmessias" 10 | }, 11 | "main": "dist/server.js", 12 | "scripts": { 13 | "build": "tsc", 14 | "changelog:last": "conventional-changelog -p angular -r 2", 15 | "changelog:preview": "conventional-changelog -p angular -u", 16 | "changelog:update": "conventional-changelog -p angular -i CHANGELOG.md -s", 17 | "clean": "shx rm -rf dist", 18 | "commit": "cz", 19 | "lint": "npx eslint --ext .ts src", 20 | "prepare": "husky install && npm run clean && npm run build", 21 | "release": "release-it", 22 | "start": "node ./dist/server.js", 23 | "watch": "tsc -w" 24 | }, 25 | "dependencies": { 26 | "@rgba-image/png": "^0.1.1", 27 | "class-transformer": "^0.5.1", 28 | "class-validator": "^0.13.2", 29 | "class-validator-jsonschema": "^3.1.1", 30 | "cors": "^2.8.5", 31 | "dotenv": "^16.1.3", 32 | "dotenv-expand": "^8.0.3", 33 | "envalid": "^7.3.1", 34 | "express": "^4.19.2", 35 | "helmet": "^5.1.1", 36 | "html-entities": "^2.3.3", 37 | "lru-cache": "^7.14.0", 38 | "metadata-scraper": "^0.2.61", 39 | "morgan": "^1.10.0", 40 | "multer": "^1.4.4", 41 | "node-fetch": "^2.6.11", 42 | "pngjs": "^6.0.0", 43 | "reflect-metadata": "^0.1.13", 44 | "routing-controllers": "^0.10.4", 45 | "routing-controllers-openapi": "^3.1.0", 46 | "swagger-ui-express": "^4.6.3", 47 | "winston": "^3.9.0", 48 | "winston-daily-rotate-file": "^4.7.1" 49 | }, 50 | "devDependencies": { 51 | "@commitlint/cli": "^17.8.1", 52 | "@commitlint/config-conventional": "^17.8.1", 53 | "@commitlint/prompt-cli": "^17.8.1", 54 | "@types/cors": "^2.8.13", 55 | "@types/express": "^4.17.21", 56 | "@types/lru-cache": "^7.10.10", 57 | "@types/morgan": "^1.9.4", 58 | "@types/multer": "^1.4.7", 59 | "@types/node": "^16.18.34", 60 | "@types/node-fetch": "^2.6.4", 61 | "@types/pngjs": "^6.0.1", 62 | "@types/swagger-ui-express": "^4.1.3", 63 | "@typescript-eslint/eslint-plugin": "^5.36.1", 64 | "@typescript-eslint/parser": "^5.36.1", 65 | "eslint": "^8.42.0", 66 | "eslint-config-prettier": "^8.8.0", 67 | "eslint-plugin-header": "^3.1.1", 68 | "eslint-plugin-import": "^2.27.5", 69 | "eslint-plugin-prettier": "^4.2.1", 70 | "eslint-plugin-simple-import-sort": "^7.0.0", 71 | "husky": "^8.0.3", 72 | "prettier": "^2.8.8", 73 | "pretty-quick": "^3.1.3", 74 | "release-it": "^15.4.1", 75 | "shx": "^0.3.4", 76 | "typescript": "^4.8.2" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2021 WPPConnect Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* eslint-disable @typescript-eslint/ban-types */ 18 | 19 | import 'reflect-metadata'; 20 | 21 | import { validationMetadatasToSchemas } from 'class-validator-jsonschema'; 22 | import express from 'express'; 23 | import helmet from 'helmet'; 24 | import morgan from 'morgan'; 25 | import { getMetadataArgsStorage, useExpressServer } from 'routing-controllers'; 26 | import { routingControllersToSpec } from 'routing-controllers-openapi'; 27 | import swaggerUi from 'swagger-ui-express'; 28 | 29 | import { LOG_FORMAT, NODE_ENV, ORIGIN, PORT, TRUST_PROXY } from './config'; 30 | import errorMiddleware from './middlewares/error.middleware'; 31 | import { logger, stream } from './utils/logger'; 32 | 33 | class App { 34 | public app: express.Application; 35 | public env: string; 36 | public port: string | number; 37 | 38 | constructor(Controllers: Function[]) { 39 | this.app = express(); 40 | this.env = NODE_ENV || 'development'; 41 | this.port = PORT || 8000; 42 | 43 | this.initializeMiddlewares(); 44 | this.initializeConfigs(); 45 | this.initializeRoutes(Controllers); 46 | this.initializeSwagger(Controllers); 47 | this.initializeErrorHandling(); 48 | } 49 | 50 | public listen() { 51 | this.app.listen(this.port, () => { 52 | logger.info(`=================================`); 53 | logger.info(`======= ENV: ${this.env} =======`); 54 | logger.info(`🚀 App listening on the port ${this.port}`); 55 | logger.info(`=================================`); 56 | }); 57 | } 58 | 59 | public getServer() { 60 | return this.app; 61 | } 62 | 63 | private initializeMiddlewares() { 64 | this.app.use(morgan(LOG_FORMAT, { stream })); 65 | this.app.use(helmet()); 66 | this.app.use(express.json()); 67 | this.app.use(express.urlencoded({ extended: true })); 68 | } 69 | 70 | private initializeConfigs() { 71 | if (/^\d+$/.test(TRUST_PROXY)) { 72 | this.app.set('trust proxy', parseInt(TRUST_PROXY)); 73 | } else { 74 | this.app.set('trust proxy', TRUST_PROXY); 75 | } 76 | 77 | console.log(this.app.get('trust proxy')); 78 | } 79 | 80 | private initializeRoutes(controllers: Function[]) { 81 | useExpressServer(this.app, { 82 | cors: { 83 | origin: ORIGIN, 84 | }, 85 | controllers: controllers, 86 | defaultErrorHandler: false, 87 | }); 88 | } 89 | 90 | private initializeSwagger(controllers: Function[]) { 91 | const schemas = validationMetadatasToSchemas({ 92 | refPointerPrefix: '#/components/schemas/', 93 | }); 94 | 95 | const routingControllersOptions = { 96 | controllers: controllers, 97 | }; 98 | 99 | const storage = getMetadataArgsStorage(); 100 | const spec = routingControllersToSpec(storage, routingControllersOptions, { 101 | components: { 102 | schemas, 103 | securitySchemes: { 104 | basicAuth: { 105 | scheme: 'basic', 106 | type: 'http', 107 | }, 108 | }, 109 | }, 110 | info: { 111 | description: 'Generated with `routing-controllers-openapi`', 112 | title: 'A sample API', 113 | version: '1.0.0', 114 | }, 115 | }); 116 | 117 | this.app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(spec)); 118 | } 119 | 120 | private initializeErrorHandling() { 121 | this.app.use(errorMiddleware); 122 | } 123 | } 124 | 125 | export default App; 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WPPConnect/WA-JS API SERVER 2 | 3 | [![npm version](https://img.shields.io/npm/v/@wppconnect/wa-js-api-server.svg?color=green)](https://www.npmjs.com/package/@wppconnect/wa-js-api-server) 4 | [![Downloads](https://img.shields.io/npm/dm/@wppconnect/wa-js-api-server.svg)](https://www.npmjs.com/package/@wppconnect/wa-js-api-server) 5 | [![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/wppconnect-team/wa-js.svg)](https://isitmaintained.com/project/wppconnect/wa-js 'Average time to resolve an issue') 6 | [![Percentage of issues still open](https://isitmaintained.com/badge/open/wppconnect-team/wa-js.svg)](https://isitmaintained.com/project/wppconnect/wa-js 'Percentage of issues still open') 7 | 8 | [![Build Status](https://img.shields.io/github/actions/workflow/status/wppconnect-team/wa-js/build.yml?branch=main)](https://github.com/wppconnect-team/wa-js/actions/workflows/build.yml) 9 | [![Build Status](https://img.shields.io/github/actions/workflow/status/wppconnect-team/wa-js/test.yml?branch=main)](https://github.com/wppconnect-team/wa-js/actions/workflows/test.yml) 10 | [![Lint Status](https://img.shields.io/github/actions/workflow/status/wppconnect-team/wa-js/lint.yml?branch=main&label=lint)](https://github.com/wppconnect-team/wa-js/actions/workflows/lint.yml) 11 | [![release-it](https://img.shields.io/badge/%F0%9F%93%A6%F0%9F%9A%80-release--it-e10079.svg)](https://github.com/release-it/release-it) 12 | 13 | > WPPConnect/WA-JS API SERVER is a small api server to provide url preview for @wppconnect/wa-js library 14 | 15 | ## Our online channels 16 | 17 | [![Discord](https://img.shields.io/discord/844351092758413353?color=blueviolet&label=Discord&logo=discord&style=flat)](https://discord.gg/JU5JGGKGNG) 18 | [![Telegram Group](https://img.shields.io/badge/Telegram-Group-32AFED?logo=telegram)](https://t.me/wppconnect) 19 | [![WhatsApp Group](https://img.shields.io/badge/WhatsApp-Group-25D366?logo=whatsapp)](https://chat.whatsapp.com/LJaQu6ZyNvnBPNAVRbX00K) 20 | [![YouTube](https://img.shields.io/youtube/channel/subscribers/UCD7J9LG08PmGQrF5IS7Yv9A?label=YouTube)](https://www.youtube.com/c/wppconnect) 21 | 22 | ## How does it work 23 | 24 | This project generate PNG images from JSON respose for URL preview. 25 | 26 | ## Starting 27 | 28 | Steps to run locally: 29 | 30 | ```bash 31 | # checkout the project 32 | git clone https://github.com/wppconnect-team/wa-js-api-server.git 33 | 34 | # enter in the folder 35 | cd wa-js-api-server 36 | 37 | # if you want to get the updates 38 | git pull 39 | 40 | # install the depencencies 41 | npm install 42 | 43 | # build javascript files 44 | npm run build 45 | 46 | # if you want to change some configuration, you can set en ENVIRONMENT variables or copy the .env to .env.local 47 | # cp .env .env.local 48 | 49 | # lauch local server 50 | node ./dist/server.js 51 | ``` 52 | 53 | ## Docker-Compose 54 | 55 | ```bash 56 | # checkout the project 57 | git clone https://github.com/wppconnect-team/wa-js-api-server.git 58 | 59 | # enter in the folder 60 | cd wa-js-api-server 61 | 62 | # if you want to change some configuration, you can set en ENVIRONMENT variables or copy the .env to .env.local 63 | # cp .env .env.local 64 | 65 | # create container 66 | docker-compose -f docker-compose.yml up --build -d 67 | ``` 68 | 69 | ## Dockerfile 70 | 71 | ```bash 72 | # checkout the project 73 | git clone https://github.com/wppconnect-team/wa-js-api-server.git 74 | 75 | # enter in the folder 76 | cd wa-js-api-server 77 | 78 | # create image 79 | docker build -t wppconnect/wa-js-api-server:1.0.0 -f Dockerfile . 80 | 81 | # create container 82 | # if you want to change some setting you can set ENVIRONMENT variables 83 | docker run -d -p 8000:8000 --name WPPconnectLinkPreview \ 84 | --restart=always \ 85 | -e NODE_ENV=production \ 86 | -e PORT=8000 \ 87 | -e LOG_FORMAT=combined \ 88 | -e LOG_DIR='./logs' \ 89 | -e ORIGIN='https://web.whatsapp.com' \ 90 | -e CACHE_MAX_ITEMS=500 \ 91 | -e CACHE_MAX_SIZE=104857600 \ 92 | -e CACHE_TTL=3600000 \ 93 | -e TRUST_PROXY=1 \ 94 | -e USER_AGENT='WhatsApp/2.2214.12 N' \ 95 | wppconnect/wa-js-api-server:1.0.0 96 | ``` 97 | 98 | ## License 99 | 100 | Copyright 2021 WPPConnect Team 101 | 102 | Licensed under the Apache License, Version 2.0 (the "License"); 103 | you may not use this file except in compliance with the License. 104 | You may obtain a copy of the License at 105 | 106 | http://www.apache.org/licenses/LICENSE-2.0 107 | 108 | Unless required by applicable law or agreed to in writing, software 109 | distributed under the License is distributed on an "AS IS" BASIS, 110 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 111 | See the License for the specific language governing permissions and 112 | limitations under the License. 113 | -------------------------------------------------------------------------------- /src/controllers/link-preview.controller.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2021 WPPConnect Team 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { Response } from 'express'; 18 | import { decode } from 'html-entities'; 19 | import getMetaData from 'metadata-scraper'; 20 | import fetch from 'node-fetch'; 21 | import { 22 | Controller, 23 | Get, 24 | HeaderParam, 25 | QueryParam, 26 | Res, 27 | } from 'routing-controllers'; 28 | import { PassThrough } from 'stream'; 29 | 30 | import { USER_AGENT } from '../config'; 31 | import { HttpException } from '../exceptions/HttpException'; 32 | import { cache } from '../utils/cache'; 33 | import { encodeToPng } from '../utils/encodeToPng'; 34 | 35 | interface LinkPreviewData { 36 | url: string; 37 | originalUrl: string; 38 | title: string; 39 | description?: string; 40 | mediaType: string; 41 | contentType?: string; 42 | image?: string; 43 | icon?: string; 44 | } 45 | 46 | @Controller('/v1/link-preview') 47 | export class LinkPreviewController { 48 | protected async fetchLinkPreviewData( 49 | url: string, 50 | acceptLanguage?: string 51 | ): Promise { 52 | acceptLanguage = acceptLanguage || 'en-US,en'; 53 | 54 | const key = `${url} - ${acceptLanguage}`; 55 | 56 | if (cache.has(key)) { 57 | return cache.get(key) || null; 58 | } 59 | 60 | const preview = await getMetaData({ 61 | url, 62 | lang: acceptLanguage, 63 | ua: USER_AGENT, 64 | }); 65 | 66 | if (!preview || !('title' in preview)) { 67 | return null; 68 | } 69 | 70 | if (preview.title) { 71 | preview.title = decode(preview.title); 72 | } 73 | 74 | if (preview.description) { 75 | preview.description = decode(preview.description); 76 | } 77 | 78 | const result = { 79 | url: preview.url!, 80 | originalUrl: url, 81 | title: preview.title!, 82 | description: preview.description, 83 | mediaType: preview.type!, 84 | contentType: (preview.contentType as string) || 'text/html', 85 | image: preview.image || preview.icon, 86 | icon: preview.icon, 87 | }; 88 | 89 | cache.set(key, result); 90 | 91 | return result; 92 | } 93 | 94 | @Get('/fetch-data.json') 95 | async fetchJson( 96 | @QueryParam('url') url: string, 97 | @HeaderParam('Accept-Language') acceptLanguage?: string 98 | ) { 99 | try { 100 | const preview = await this.fetchLinkPreviewData(url, acceptLanguage); 101 | 102 | if (!preview) { 103 | return { 104 | status: 404, 105 | message: `Preview link not found for "${url}"`, 106 | error: true, 107 | }; 108 | } 109 | 110 | return { 111 | status: 200, 112 | ...preview, 113 | }; 114 | } catch (error: any) { 115 | const status: number = error.status || 500; 116 | const message: string = error.message || 'Something went wrong'; 117 | 118 | return { 119 | status, 120 | message, 121 | error: true, 122 | }; 123 | } 124 | } 125 | 126 | @Get('/fetch-data.png') 127 | async fetchPng( 128 | @QueryParam('url') url: string, 129 | @Res() response: Response, 130 | @HeaderParam('Accept-Language') acceptLanguage?: string 131 | ) { 132 | const preview = await this.fetchJson(url, acceptLanguage); 133 | 134 | const buffer = encodeToPng(JSON.stringify(preview)); 135 | 136 | const readStream = new PassThrough(); 137 | 138 | readStream.end(buffer); 139 | 140 | response.setHeader('Content-Type', 'image/png'); 141 | response.setHeader('Content-Length', buffer.length); 142 | 143 | return readStream.pipe(response); 144 | } 145 | 146 | @Get('/download-image') 147 | async downloadImage( 148 | @QueryParam('url') url: string, 149 | @Res() response: Response 150 | ) { 151 | const data = await fetch(url, { 152 | headers: { 153 | 'User-Agent': USER_AGENT, 154 | }, 155 | }); 156 | 157 | if (!data.ok) { 158 | throw new HttpException(404, `image not found for "${url}"`); 159 | } 160 | 161 | const mimeType = 162 | data.headers.get('content-type') || 'application/octet-stream'; 163 | 164 | if (!/^image\//.test(mimeType)) { 165 | throw new HttpException( 166 | 400, 167 | `The content of "${url}" is not an image, current mime type: "${mimeType}"` 168 | ); 169 | } 170 | 171 | const headers: { [key: string]: string } = {}; 172 | if (data.headers.has('Content-Type')) { 173 | headers['Content-Type'] = data.headers.get('Content-Type')!; 174 | } 175 | if (data.headers.has('Content-Length')) { 176 | headers['Content-Length'] = data.headers.get('Content-Length')!; 177 | } 178 | 179 | response.writeHead(200, headers); 180 | 181 | return data.body?.pipe(response); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2021 WPPConnect Team 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. --------------------------------------------------------------------------------