├── .editorconfig ├── .eslintrc.js ├── .eslintrc.prepublish.js ├── .gitignore ├── .prettierrc.js ├── .vscode ├── extensions.json └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── SECURITY.md ├── assets ├── codechat_credentials.png ├── codechat_search_bar.png ├── cover.png ├── resource_send_message.png ├── settings_community_nodes.png └── settings_community_nodes_codechat.png ├── credentials └── CodeChatCredentialsApi.credentials.ts ├── gulpfile.js ├── nodes ├── .gitkeep └── CodeChat │ ├── CodeChat.d.ts │ ├── CodeChat.fields.ts │ ├── CodeChat.node.json │ ├── CodeChat.node.ts │ ├── Generic.func.ts │ ├── codechat.svg │ └── descriptions │ ├── Chat.desc.ts │ ├── Group.desc.ts │ └── SendMessage.desc.ts ├── package-lock.json ├── package.json ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [package.json] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | 18 | [*.yml] 19 | indent_style = space 20 | indent_size = 2 21 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | 4 | env: { 5 | browser: true, 6 | es6: true, 7 | node: true, 8 | }, 9 | 10 | parser: '@typescript-eslint/parser', 11 | parserOptions: { 12 | project: ['./tsconfig.json'], 13 | sourceType: 'module', 14 | extraFileExtensions: ['.json'], 15 | }, 16 | ignorePatterns: [ 17 | '.eslintrc.js', 18 | '**/*.js', 19 | '**/node_modules/**', 20 | '**/dist/**', 21 | ], 22 | 23 | overrides: [ 24 | { 25 | files: ['package.json'], 26 | plugins: ['eslint-plugin-n8n-nodes-base/'], 27 | extends: ['plugin:n8n-nodes-base/community'], 28 | rules: { 29 | 'n8n-nodes-base/community-package-json-name-still-default': 'off', 30 | } 31 | }, 32 | { 33 | files: ['./credentials/**/*.ts'], 34 | plugins: ['eslint-plugin-n8n-nodes-base'], 35 | extends: ['plugin:n8n-nodes-base/credentials'], 36 | rules: { 37 | 'n8n-nodes-base/cred-class-field-documentation-url-missing': 'off', 38 | 'n8n-nodes-base/cred-class-field-documentation-url-miscased': 'off', 39 | }, 40 | }, 41 | { 42 | files: ['./nodes/**/*.ts'], 43 | plugins: ['eslint-plugin-n8n-nodes-base'], 44 | extends: ['plugin:n8n-nodes-base/nodes'], 45 | rules: { 46 | 'n8n-nodes-base/node-execute-block-missing-continue-on-fail': 'off', 47 | 'n8n-nodes-base/node-resource-description-filename-against-convention': 'off', 48 | 'n8n-nodes-base/node-param-fixed-collection-type-unsorted-items': 'off', 49 | 'n8n-nodes-base/node-execute-block-operation-missing-singular-pairing': 'off', 50 | 'n8n-nodes-base/node-execute-block-operation-missing-plural-pairing': 'off', 51 | }, 52 | }, 53 | ], 54 | }; 55 | -------------------------------------------------------------------------------- /.eslintrc.prepublish.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: './.eslintrc.js', 3 | overrides: [ 4 | { 5 | files: ['package.json'], 6 | plugins: ['eslint-plugin-n8n-nodes-base'], 7 | rules: { 8 | 'n8n-nodes-base/community-package-json-name-still-default': 'error', 9 | }, 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .tmp 4 | tmp 5 | dist 6 | npm-debug.log* 7 | yarn.lock 8 | .vscode/launch.json 9 | .npmrc 10 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 3 | * https://prettier.io/docs/en/options.html#semicolons 4 | */ 5 | semi: true, 6 | 7 | /** 8 | * https://prettier.io/docs/en/options.html#trailing-commas 9 | */ 10 | trailingComma: 'all', 11 | 12 | /** 13 | * https://prettier.io/docs/en/options.html#bracket-spacing 14 | */ 15 | bracketSpacing: true, 16 | 17 | /** 18 | * https://prettier.io/docs/en/options.html#tabs 19 | */ 20 | useTabs: true, 21 | 22 | /** 23 | * https://prettier.io/docs/en/options.html#tab-width 24 | */ 25 | tabWidth: 2, 26 | 27 | /** 28 | * https://prettier.io/docs/en/options.html#arrow-function-parentheses 29 | */ 30 | arrowParens: 'always', 31 | 32 | /** 33 | * https://prettier.io/docs/en/options.html#quotes 34 | */ 35 | singleQuote: true, 36 | 37 | /** 38 | * https://prettier.io/docs/en/options.html#quote-props 39 | */ 40 | quoteProps: 'as-needed', 41 | 42 | /** 43 | * https://prettier.io/docs/en/options.html#end-of-line 44 | */ 45 | endOfLine: 'lf', 46 | 47 | /** 48 | * https://prettier.io/docs/en/options.html#print-width 49 | */ 50 | printWidth: 100, 51 | }; 52 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "EditorConfig.EditorConfig", 5 | "esbenp.prettier-vscode", 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.fontSize": 13, 3 | "editor.fontLigatures": true, 4 | "editor.letterSpacing": 0.5, 5 | "editor.smoothScrolling": true, 6 | "editor.tabSize": 2, 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll.eslint": true, 9 | "source.fixAll": true 10 | } 11 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at jan@n8n.io. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2022 n8n 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | [![Telegram](https://img.shields.io/badge/Group-Telegram-%2333C1FF)](https://t.me/codechatBR) 4 | [![Whatsapp](https://img.shields.io/badge/WhatsApp-message-%2322BC18)](https://api.whatsapp.com/send?phone=5531995918699) 5 | ![n8n-node-codechat](https://img.shields.io/github/package-json/v/code-chat-br/n8n-nodes-codechat?label=n8n-node-codechat&logo=codechat) 6 | ![License](https://img.shields.io/github/license/code-chat-br/n8n-nodes-codechat) 7 | [![n8n](https://img.shields.io/badge/n8n-community-F94B72)](https://community.n8n.io/) 8 | ![npm](https://img.shields.io/npm/dt/n8n-nodes-codechat) 9 | ![npm](https://img.shields.io/npm/dw/n8n-nodes-codechat?label=%20) 10 | 11 |
12 | 13 |
14 | 15 | # n8n-nodes-codechat 16 | 17 | This is a [N8n](https://community.n8n.io/) community node. It allows you to use CodeChat Api to communicate with WhatsApp in your workflow. 18 | 19 | You can use most features provided by CodeChat. Like sending messages, creating and manipulating groups and managing chats. 20 | 21 | # Instalation 22 | 23 | 1. Go to **Settings** > **Community Nodes**, and Click Install a community node. 24 | ![Community nodes](./assets/settings_community_nodes.png) 25 | 26 | 2. Enter `n8n-nodes-codechat` in the **npm Package Name** field. And click **Install**. 27 | ![Package Name](./assets/settings_community_nodes_codechat.png) 28 | 29 | Once installed, you can use the search bar to add the CodeChat node to your workflow. 30 | ![codechat_search_bar](./assets/codechat_search_bar.png) 31 | 32 | ![Resource Send Message](./assets/resource_send_message.png) 33 | 34 | # Usage 35 | 36 | Node configuration is quite simple, just specify the credentials and use the available resources and operations. 37 | 38 | # Credentials 39 | 40 | All operations require a credential which is composed of the **Api Key**, **Instance Name** and the call **Base Url**. Which will be made available at the time of contracting the service. 41 | ![Credentials Codechat](./assets/codechat_credentials.png) 42 | 43 | # Resources 44 | 45 | This section describes which **CodeChat API** operations are available on this node. 46 | 47 | ## Resource Send Message 48 | 49 | | Operation | Method | Credentials required | 50 | | --------------------|--------|----------------------| 51 | | Send Text | POST | True | 52 | | Send Buttons | POST | True | 53 | | Send Template | POST | True | 54 | | Send Media | POST | True | 55 | | Send Media Base64 | POST | True | 56 | | send Link Preview | POST | True | 57 | | Send Contact | POST | True | 58 | | Send List | POST | True | 59 | | Send WhatsApp Audio | POST | True | 60 | 61 | All operations return a queue for tracking the shipment that can be monitored through the webhook. 62 | 63 | ```json 64 | { 65 | "header": { 66 | "queueId": "20aa6e5f-4545-4f90-b712-9a176c9509da", 67 | "status": "PROCESSING", 68 | "progress": 0 69 | }, 70 | "data": { 71 | "jids": [ 72 | "5531900000000@s.whatsapp.net" 73 | ] 74 | } 75 | } 76 | ``` 77 | 78 | ## Resource Group 79 | 80 | | Operation | Method | Credentials required | Description | 81 | | -----------------------|--------|----------------------|---------------------------------------------------------------| 82 | | Accept Invite | PUT | True | Accept incoming group invite | 83 | | Change Expiration | PUT | True | Sets the expiration of group messages | 84 | | Create Group | POST | True | | 85 | | Fetch Participants | GET | True | | 86 | | Group Metadata | GET | True | Retrieve all information about the group and its participants | 87 | | Invite Code | GET | True | Generates the created group invitation | 88 | | Revoke Invite | PUT | True | | 89 | | Update Info | PUT | True | Update the group's subject or description | 90 | | Update Participants | PUT | True | | 91 | | Update Profile Picture | PUT | True | | 92 | | Update Settings | PUT | True | Update group chat and group handling rules | 93 | 94 | # Resource Chat 95 | 96 | | Operation | Method | Credentials required | Description | 97 | |----------------------|--------|----------------------|-------------------------------------------| 98 | | Block Contact | PUT | True | | 99 | | Business Profile | GET | True | Retrieve business contact information | 100 | | Mark Message As Read | PUT | True | | 101 | | Delete Message | DELETE | True | Delete for me or everyone | 102 | | On WhatsApp | POST | True | Check if the contact is on WhatsApp | 103 | | Profile Picture Url | GET | True | Retrieve a contact's profile picture | 104 | | Update Presence | PUT | True | compising - recording - available - pause | 105 | | Update Status | PUT | True | | 106 | 107 | # Compatibility 108 | 109 | Tested on n8n version 0.198.2 and CodeChat version 2.0.0 110 | 111 | ## License 112 | 113 | [MIT](https://github.com/code-chat-br/n8n-node-codechat/blob/main/LICENSE.md) 114 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /assets/codechat_credentials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-chat-br/n8n-nodes-codechat/d0ecca9de4a1e195c1e791429fe5acdcd7f8219c/assets/codechat_credentials.png -------------------------------------------------------------------------------- /assets/codechat_search_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-chat-br/n8n-nodes-codechat/d0ecca9de4a1e195c1e791429fe5acdcd7f8219c/assets/codechat_search_bar.png -------------------------------------------------------------------------------- /assets/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-chat-br/n8n-nodes-codechat/d0ecca9de4a1e195c1e791429fe5acdcd7f8219c/assets/cover.png -------------------------------------------------------------------------------- /assets/resource_send_message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-chat-br/n8n-nodes-codechat/d0ecca9de4a1e195c1e791429fe5acdcd7f8219c/assets/resource_send_message.png -------------------------------------------------------------------------------- /assets/settings_community_nodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-chat-br/n8n-nodes-codechat/d0ecca9de4a1e195c1e791429fe5acdcd7f8219c/assets/settings_community_nodes.png -------------------------------------------------------------------------------- /assets/settings_community_nodes_codechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-chat-br/n8n-nodes-codechat/d0ecca9de4a1e195c1e791429fe5acdcd7f8219c/assets/settings_community_nodes_codechat.png -------------------------------------------------------------------------------- /credentials/CodeChatCredentialsApi.credentials.ts: -------------------------------------------------------------------------------- 1 | import { ICredentialType, INodeProperties } from 'n8n-workflow'; 2 | 3 | export class CodeChatCredentialsApi implements ICredentialType { 4 | name = 'codeChatApi'; 5 | displayName = 'CodeChat API'; 6 | properties: INodeProperties[] = [ 7 | { 8 | displayName: 'Instance Name', 9 | name: 'instanceName', 10 | type: 'string', 11 | default: '', 12 | required: true, 13 | description: 'Name of the instance informed in the registration', 14 | }, 15 | 16 | { 17 | displayName: 'Authorization (apiKey)', 18 | name: 'apiKey', 19 | type: 'string', 20 | default: '', 21 | required: true, 22 | description: 'System generated license code', 23 | }, 24 | 25 | { 26 | displayName: 'Base URL', 27 | name: 'baseUrl', 28 | type: 'string', 29 | required: true, 30 | default: '', 31 | placeholder: 'https://api.codechat.rest', 32 | hint: 'Inform the url provided by your service provider', 33 | }, 34 | ]; 35 | documentationUrl = 'https://docs.codechat.dev/'; 36 | } 37 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { task, src, dest } = require('gulp'); 3 | 4 | task('build:icons', copyIcons); 5 | 6 | function copyIcons() { 7 | const nodeSource = path.resolve('nodes', '**', '*.{png,svg}'); 8 | const nodeDestination = path.resolve('dist', 'nodes'); 9 | 10 | src(nodeSource).pipe(dest(nodeDestination)); 11 | 12 | const credSource = path.resolve('credentials', '**', '*.{png,svg}'); 13 | const credDestination = path.resolve('dist', 'credentials'); 14 | 15 | return src(credSource).pipe(dest(credDestination)); 16 | } 17 | -------------------------------------------------------------------------------- /nodes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code-chat-br/n8n-nodes-codechat/d0ecca9de4a1e195c1e791429fe5acdcd7f8219c/nodes/.gitkeep -------------------------------------------------------------------------------- /nodes/CodeChat/CodeChat.d.ts: -------------------------------------------------------------------------------- 1 | 2 | export namespace RequestBody { 3 | export type IError = { 4 | error: string | string[]; 5 | message: string; 6 | statusCode: number; 7 | }; 8 | 9 | export type INumbers = { 10 | numbers: string; 11 | }; 12 | 13 | export type IOptions = { 14 | options: { 15 | messageId: string; 16 | mentions: { 17 | everyone: boolean | string, 18 | mentioned: string[], 19 | }; 20 | delay: number; 21 | }; 22 | }; 23 | 24 | export type IMediaData = { 25 | type: 'document' | 'image' | 'video' | 'sticker'; 26 | source: string 27 | } 28 | 29 | export type IMedia = { 30 | caption?: string; 31 | url: string; 32 | mediaType: 'document' | 'image' | 'video' | 'sticker'; 33 | } 34 | 35 | export type IButtons = { 36 | displayText: string; 37 | buttonId: string; 38 | } 39 | 40 | export type IButtonsMessage = { 41 | mediaData?: IMediaData; 42 | buttons?: { 43 | replyButtons: IButtons[] 44 | }; 45 | buttonsMessage: { 46 | title: string; 47 | description: string; 48 | footerText?: string; 49 | buttons: IButtons[]; 50 | mediaMessage?: IMedia 51 | }; 52 | }; 53 | 54 | export type ITemplateButtons = { 55 | buttonType: 'urlButton' | 'replyButton' | 'callButton'; 56 | displayText: string; 57 | payload: string; 58 | }; 59 | 60 | export type ITemplateMessage = { 61 | mediaData?: IMediaData; 62 | buttons?: { 63 | templateButtons: ITemplateButtons[]; 64 | }; 65 | templateMessage: { 66 | title: string; 67 | description: string; 68 | footerText?: string; 69 | buttons: ITemplateButtons[]; 70 | mediaMessage?: IMedia 71 | }; 72 | }; 73 | 74 | export type IRow = { 75 | title: string; 76 | description: string; 77 | rowId: string; 78 | }; 79 | 80 | export type Isection = 81 | | { 82 | title: string; 83 | rows: IRow[]; 84 | } & { rowsProperty?: { rows: IRow[] } }; 85 | 86 | export type IListMessage = { 87 | listMessage: { 88 | title: string; 89 | description: string; 90 | footerText: string; 91 | buttonText: string; 92 | sections: Isection[]; 93 | }; 94 | }; 95 | 96 | export type IContactMessage = { 97 | contactMessage: { 98 | contacts: { 99 | wuid: string; 100 | phoneNumber: string; 101 | fullName: string; 102 | }[] 103 | } 104 | } 105 | 106 | export type IReadMessage = { 107 | readMessage: { 108 | messageId: string; 109 | fromMe: boolean; 110 | wuid: string; 111 | } 112 | }; 113 | 114 | export type ICreateGroup = { 115 | participants: string | string[]; 116 | groupSubject: string; 117 | groupDescription: string; 118 | profilePicture?: string; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /nodes/CodeChat/CodeChat.fields.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import { 3 | blockCobtactProperties, 4 | budinessProfileProperties, 5 | deleteMessageProperties, 6 | markMSGReadProperties, 7 | onWhatsappProperties, 8 | profilePictureProperties, 9 | statusContactPorperties, 10 | updatePresence, 11 | updateStatusProperties, 12 | } from './descriptions/Chat.desc'; 13 | import { 14 | acceptInviteCodeProperties, 15 | changeExpirationProperties, 16 | createGroupPrperties, 17 | fetchParticipantsProperties, 18 | groupInviteCodeProperties, 19 | groupMetadataProperties, 20 | leaveGroupProperties, 21 | revokeInviteCodeProperties, 22 | updateGroupProperties, 23 | updateParticipantsPorperties, 24 | updatePropfilePictureProperties, 25 | updateRuleGroupProperties, 26 | } from './descriptions/Group.desc'; 27 | import { 28 | buttonsProperties, 29 | contactProperties, 30 | linkPreviewProperties, 31 | listProperties, 32 | locationProperties, 33 | mediaBase64MessgeProperties, 34 | mediaMessageProperties, 35 | optionsProperties, 36 | templateProperties, 37 | textProperties, 38 | whatsAppAudioProperties, 39 | } from './descriptions/SendMessage.desc'; 40 | import { formatNumber } from './Generic.func'; 41 | 42 | const messageResource: INodeProperties[] = [ 43 | { 44 | displayName: 'List Recipient Phone Numbers', 45 | name: 'listPhoneNumbers', 46 | type: 'json', 47 | default: [], 48 | placeholder: `[Array:['5531900000000, '5521911111111']] or 5531922222222`, 49 | description: 'This field supports both a list and a single number', 50 | hint: 'When entering a phone number, make sure to include the country code', 51 | routing: { send: { type: 'body', property: 'numbers', preSend: [formatNumber] } }, 52 | displayOptions: { show: { resource: ['sendMessage'] } }, 53 | }, 54 | 55 | /**┌──────────────────────────────┐ 56 | * │ Options Properties │ 57 | * └──────────────────────────────┘ 58 | */ 59 | ...optionsProperties, 60 | 61 | { 62 | displayName: 'Operation', 63 | name: 'operation', 64 | noDataExpression: true, 65 | placeholder: '', 66 | required: true, 67 | type: 'options', 68 | options: [ 69 | { 70 | name: 'Send Buttons', 71 | value: 'sendButtons', 72 | action: 'Send buttons a send message', 73 | }, 74 | { 75 | name: 'Send Contact', 76 | value: 'sendContact', 77 | action: 'Send contact a send message', 78 | }, 79 | { 80 | name: 'Send Link Preview', 81 | value: 'sendLinkPreview', 82 | action: 'Send link preview a send message', 83 | }, 84 | { 85 | name: 'Send List', 86 | value: 'sendList', 87 | action: 'Send list a send message', 88 | }, 89 | { 90 | name: 'Send Location', 91 | value: 'sendLocation', 92 | action: 'Send location a send message', 93 | }, 94 | { 95 | name: 'Send Media', 96 | value: 'sendMedia', 97 | action: 'Send media a send message', 98 | }, 99 | { 100 | name: 'Send Media Base64', 101 | value: 'sendMediaBase64', 102 | action: 'Send media base64 a send message', 103 | }, 104 | { 105 | name: 'Send Template', 106 | value: 'sendTemplate', 107 | action: 'Send template a send message', 108 | }, 109 | { 110 | name: 'Send Text', 111 | value: 'sendText', 112 | action: 'Send text a send message', 113 | }, 114 | { 115 | name: 'Send WhatsApp Audio', 116 | value: 'sendWhatsAppAudio', 117 | action: 'Send whats app audio a send message', 118 | }, 119 | ], 120 | default: 'sendText', 121 | displayOptions: { show: { resource: ['sendMessage'] } }, 122 | }, 123 | 124 | /**┌───────────────────────────┐ 125 | * │ Text Properties │ 126 | * └───────────────────────────┘ 127 | */ 128 | ...textProperties, 129 | 130 | /**┌──────────────────────────────┐ 131 | * │ Buttons Properties │ 132 | * └──────────────────────────────┘ 133 | */ 134 | ...buttonsProperties, 135 | 136 | /**┌───────────────────────────────┐ 137 | * │ Template Properties │ 138 | * └───────────────────────────────┘ 139 | */ 140 | ...templateProperties, 141 | 142 | /**┌────────────────────────────┐ 143 | * │ Media Properties │ 144 | * └────────────────────────────┘ 145 | */ 146 | ...mediaMessageProperties, 147 | 148 | /**┌───────────────────────────────────┐ 149 | * │ Media Base64 Properties │ 150 | * └───────────────────────────────────┘ 151 | */ 152 | ...mediaBase64MessgeProperties, 153 | 154 | /**┌───────────────────────────────┐ 155 | * │ WhatsApp Properties │ 156 | * └───────────────────────────────┘ 157 | */ 158 | ...whatsAppAudioProperties, 159 | 160 | /**┌───────────────────────────────┐ 161 | * │ Location Properties │ 162 | * └───────────────────────────────┘ 163 | */ 164 | ...locationProperties, 165 | 166 | /**┌───────────────────────────┐ 167 | * │ List Properties │ 168 | * └───────────────────────────┘ 169 | */ 170 | ...listProperties, 171 | 172 | /**┌───────────────────────────────────┐ 173 | * │ Link Preview Properties │ 174 | * └───────────────────────────────────┘ 175 | */ 176 | ...linkPreviewProperties, 177 | 178 | /**┌───────────────────────────────┐ 179 | * │ Contact Properties │ 180 | * └───────────────────────────────┘ 181 | */ 182 | ...contactProperties, 183 | ]; 184 | 185 | const chatResource: INodeProperties[] = [ 186 | { 187 | displayName: 'Operation', 188 | name: 'operation', 189 | noDataExpression: true, 190 | placeholder: '', 191 | required: true, 192 | type: 'options', 193 | options: [ 194 | { 195 | name: 'Block Contact', 196 | value: 'blockContact', 197 | action: 'Block contact a chat', 198 | }, 199 | { 200 | name: 'Business Profile', 201 | value: 'businesProfile', 202 | action: 'Business profile a chat', 203 | }, 204 | { 205 | name: 'Contact Status', 206 | value: 'contactStatus', 207 | action: 'Contact status a chat', 208 | }, 209 | { 210 | name: 'Delete Message', 211 | value: 'deleteMessage', 212 | action: 'Delete message a chat', 213 | }, 214 | { 215 | name: 'Mark Message As Read', 216 | value: 'markMessageAsRead', 217 | action: 'Mark message as read a chat', 218 | }, 219 | { 220 | name: 'On WhatsApp', 221 | value: 'onWhatsApp', 222 | action: 'On whats app a chat', 223 | }, 224 | { 225 | name: 'Profile Picture Url', 226 | value: 'profilePictureUrl', 227 | action: 'Profile picture url a chat', 228 | }, 229 | { 230 | name: 'Update Presence', 231 | value: 'updatePresence', 232 | action: 'Update presence a chat', 233 | }, 234 | { 235 | name: 'Update Status', 236 | value: 'updateStatus', 237 | action: 'Update status a chat', 238 | }, 239 | ], 240 | default: 'onWhatsApp', 241 | routing: { request: { ignoreHttpStatusErrors: true } }, 242 | displayOptions: { show: { resource: ['chat'] } }, 243 | }, 244 | 245 | /**┌───────────────────────────────────┐ 246 | * │ On WhatsApp Properties │ 247 | * └───────────────────────────────────┘ 248 | */ 249 | ...onWhatsappProperties, 250 | 251 | /**┌───────────────────────────────────────┐ 252 | * │ Update Presence Properties │ 253 | * └───────────────────────────────────────┘ 254 | */ 255 | ...updatePresence, 256 | 257 | /**┌───────────────────────────────────┐ 258 | * │ Read Message Properties │ 259 | * └───────────────────────────────────┘ 260 | */ 261 | ...markMSGReadProperties, 262 | 263 | /**┌────────────────────────────────────┐ 264 | * │ Delete Messge Properties │ 265 | * └────────────────────────────────────┘ 266 | */ 267 | ...deleteMessageProperties, 268 | 269 | /**┌────────────────────────────────────┐ 270 | * │ Block Contact Properties │ 271 | * └────────────────────────────────────┘ 272 | */ 273 | ...blockCobtactProperties, 274 | 275 | /**┌─────────────────────────────────────┐ 276 | * │ Status Contact Properties │ 277 | * └─────────────────────────────────────┘ 278 | */ 279 | ...statusContactPorperties, 280 | 281 | /**┌────────────────────────────────────┐ 282 | * │ Update Status Properties │ 283 | * └────────────────────────────────────┘ 284 | */ 285 | ...updateStatusProperties, 286 | 287 | /**┌───────────────────────────────────────┐ 288 | * │ Business Profile Properties │ 289 | * └───────────────────────────────────────┘ 290 | */ 291 | ...budinessProfileProperties, 292 | 293 | /**┌──────────────────────────────────────┐ 294 | * │ Profile Picture Properties │ 295 | * └──────────────────────────────────────┘ 296 | */ 297 | ...profilePictureProperties, 298 | ]; 299 | 300 | const groupResource: INodeProperties[] = [ 301 | { 302 | displayName: 'Operation', 303 | name: 'operation', 304 | required: true, 305 | noDataExpression: true, 306 | type: 'options', 307 | options: [ 308 | { 309 | name: 'Accept Invite', 310 | value: 'acceptInvite', 311 | action: 'Accept invite a group', 312 | }, 313 | { 314 | name: 'Change Expiration', 315 | value: 'changeExpiration', 316 | action: 'Change expiration a group', 317 | }, 318 | { 319 | name: 'Create Group', 320 | value: 'createGroup', 321 | action: 'Create group a group', 322 | }, 323 | { 324 | name: 'Fetch Participants', 325 | value: 'fetchParticpants', 326 | action: 'Fetch participants a group', 327 | }, 328 | { 329 | name: 'Group Metadata', 330 | value: 'groupMetadata', 331 | action: 'Group metadata a group', 332 | }, 333 | { 334 | name: 'Invite Code', 335 | value: 'inviteCode', 336 | action: 'Invite code a group', 337 | }, 338 | { 339 | name: 'Leave the Group', 340 | value: 'leaveGroup', 341 | action: 'Leave the group a group', 342 | }, 343 | { 344 | name: 'Revoke Invite', 345 | value: 'revokeInvite', 346 | action: 'Revoke invite a group', 347 | }, 348 | { 349 | name: 'Update Info', 350 | value: 'updateInfo', 351 | action: 'Update info a group', 352 | }, 353 | { 354 | name: 'Update Participants', 355 | value: 'updateParticipants', 356 | action: 'Update participants a group', 357 | }, 358 | { 359 | name: 'Update Profile Picture', 360 | value: 'updatePicture', 361 | action: 'Update profile picture a group', 362 | }, 363 | { 364 | name: 'Update Rules', 365 | value: 'updateRule', 366 | action: 'Update rules a group', 367 | }, 368 | ], 369 | default: 'createGroup', 370 | displayOptions: { 371 | show: { resource: ['group'] }, 372 | }, 373 | }, 374 | 375 | /**┌───────────────────────────────────┐ 376 | * │ Create Group Properties │ 377 | * └───────────────────────────────────┘ 378 | */ 379 | ...createGroupPrperties, 380 | 381 | /**┌──────────────────────────────────┐ 382 | * │ Invite Code Properties │ 383 | * └──────────────────────────────────┘ 384 | */ 385 | ...groupInviteCodeProperties, 386 | 387 | /**┌────────────────────────────────────┐ 388 | * │ Accept Invite Properties │ 389 | * └────────────────────────────────────┘ 390 | */ 391 | ...acceptInviteCodeProperties, 392 | 393 | /**┌────────────────────────────────────┐ 394 | * │ Revoke Invite Properties │ 395 | * └────────────────────────────────────┘ 396 | */ 397 | ...revokeInviteCodeProperties, 398 | 399 | /**┌───────────────────────────────────┐ 400 | * │ Update Group Properties │ 401 | * └───────────────────────────────────┘ 402 | */ 403 | ...updateGroupProperties, 404 | 405 | /**┌─────────────────────────────────────────────┐ 406 | * │ Update Profile Picture Properties │ 407 | * └─────────────────────────────────────────────┘ 408 | */ 409 | ...updatePropfilePictureProperties, 410 | 411 | /**┌─────────────────────────────────────┐ 412 | * │ Group Metadata Properties │ 413 | * └─────────────────────────────────────┘ 414 | */ 415 | ...groupMetadataProperties, 416 | 417 | /**┌──────────────────────────────────────────┐ 418 | * │ Update Participants Properties │ 419 | * └──────────────────────────────────────────┘ 420 | */ 421 | ...updateParticipantsPorperties, 422 | 423 | /**┌──────────────────────────────────────┐ 424 | * │ Update Settings Properties │ 425 | * └──────────────────────────────────────┘ 426 | */ 427 | ...updateRuleGroupProperties, 428 | 429 | /**┌────────────────────────────────────────┐ 430 | * │ Change Expiration Properties │ 431 | * └────────────────────────────────────────┘ 432 | */ 433 | ...changeExpirationProperties, 434 | 435 | /**┌────────────────────────────────────────────┐ 436 | * │ Retrieve Participants Properties │ 437 | * └────────────────────────────────────────────┘ 438 | */ 439 | ...fetchParticipantsProperties, 440 | 441 | /**┌──────────────────────────────────┐ 442 | * │ Leave Group Properties │ 443 | * └──────────────────────────────────┘ 444 | */ 445 | ...leaveGroupProperties, 446 | ]; 447 | 448 | export const codechatFields: INodeProperties[] = [ 449 | /**┌─────────────────────────────┐ 450 | * │ Resource: Message │ 451 | * └─────────────────────────────┘ 452 | */ 453 | ...messageResource, 454 | 455 | /**┌──────────────────────────┐ 456 | * │ Resource: Caht │ 457 | * └──────────────────────────┘ 458 | */ 459 | ...chatResource, 460 | 461 | /**┌───────────────────────────┐ 462 | * │ Resource: Group │ 463 | * └───────────────────────────┘ 464 | */ 465 | ...groupResource, 466 | ]; 467 | -------------------------------------------------------------------------------- /nodes/CodeChat/CodeChat.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "node": "n8n-nodes-base.codechat", 3 | "nodeVersion": "1.0", 4 | "codexVersion": "1.0", 5 | "categories": ["Communication","Development"], 6 | "resources": { 7 | "credentialDocumentation": [ 8 | { 9 | "url": "https://docs.codechat.dev/" 10 | } 11 | ], 12 | "primaryDocumentation": [ 13 | { 14 | "url": "https://docs.codechat.dev/" 15 | } 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /nodes/CodeChat/CodeChat.node.ts: -------------------------------------------------------------------------------- 1 | import { INodeType, INodeTypeDescription } from 'n8n-workflow'; 2 | import { codechatFields } from './CodeChat.fields'; 3 | import { sendErrorPostReceive } from './Generic.func'; 4 | 5 | export class CodeChat implements INodeType { 6 | description: INodeTypeDescription = { 7 | displayName: 'CodeChat - WhatsApp Api Cloud', 8 | name: 'codeChat', 9 | icon: 'file:codechat.svg', 10 | group: ['output'], 11 | version: 1, 12 | subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}', 13 | description: 'Rest api for communication with WhatsApp', 14 | defaults: { name: 'CodeChat' }, 15 | credentials: [{ name: 'codeChatApi', required: true }], 16 | inputs: ['main'], 17 | outputs: ['main'], 18 | requestDefaults: { 19 | baseURL: '={{$credentials.baseUrl}}', 20 | headers: { 21 | apiKey: '={{$credentials.apiKey}}', 22 | }, 23 | ignoreHttpStatusErrors: true, 24 | }, 25 | properties: [ 26 | { 27 | displayName: 'Resource', 28 | required: true, 29 | name: 'resource', 30 | type: 'options', 31 | noDataExpression: true, 32 | options: [ 33 | { name: 'Send Message', value: 'sendMessage' }, 34 | { name: 'Group', value: 'group' }, 35 | { name: 'Chat', value: 'chat' }, 36 | ], 37 | default: 'sendMessage', 38 | routing: { output: { postReceive: [sendErrorPostReceive] } }, 39 | }, 40 | 41 | ...codechatFields, 42 | ], 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /nodes/CodeChat/Generic.func.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IExecuteSingleFunctions, 3 | IHttpRequestOptions, 4 | IN8nHttpFullResponse, 5 | INodeExecutionData, 6 | NodeApiError, 7 | } from 'n8n-workflow'; 8 | import { RequestBody } from './CodeChat'; 9 | 10 | export async function sendErrorPostReceive( 11 | this: IExecuteSingleFunctions, 12 | data: INodeExecutionData[], 13 | response: IN8nHttpFullResponse, 14 | ): Promise { 15 | const body = response?.body as RequestBody.IError; 16 | if (body?.error) { 17 | const node = this.getNode(); 18 | const creds = await this.getCredentials('codeChatApi'); 19 | Object.assign(node.credentials as {}, { creds }); 20 | 21 | throw { 22 | httpCode: body.statusCode, 23 | context: body.error, 24 | cause: { error:body.error, message:body.message }, 25 | node: this.getNode(), 26 | workflow: this.getWorkflow(), 27 | message: `${body.error} - statusCode ${body.statusCode}`, 28 | description: 'Check the type of properties and values entered', 29 | }; 30 | } 31 | return data; 32 | } 33 | 34 | function isNotempty(value: string) { 35 | if (!value) return false; 36 | if (typeof value === 'string' && value === '') return false; 37 | return true; 38 | } 39 | 40 | function join(...paths: string[]) { 41 | let url = ''; 42 | paths.forEach((path) => (url += `${path}/`)); 43 | return url; 44 | } 45 | 46 | export function requestURL(...paths: string[]) { 47 | return join('{{$credentials.instanceName}}', ...paths); 48 | } 49 | 50 | export async function formatNumber( 51 | this: IExecuteSingleFunctions, 52 | requestOptions: IHttpRequestOptions, 53 | ): Promise { 54 | const body = requestOptions.body as RequestBody.INumbers & RequestBody.IError; 55 | 56 | const node = this.getNode(); 57 | const creds = await this.getCredentials('codeChatApi'); 58 | Object.assign(node.credentials as {}, creds); 59 | 60 | const numbers: string[] = []; 61 | 62 | if (!Array.isArray(body.numbers)) { 63 | if ( 64 | !body?.numbers || 65 | typeof body.numbers !== 'string' || 66 | !(body.numbers as string).match(/\d+/g) 67 | ) { 68 | throw { 69 | httpCode: '400', 70 | context: 'Bad Request', 71 | cause: { 72 | error: [ 73 | 'listPhoneNumbers cannot be empty', 74 | 'listPhoneNumbers must be a numeric string', 75 | ], 76 | message: 'Check the type of properties and values entered', 77 | }, 78 | node, 79 | workflow: this.getWorkflow(), 80 | message: `${'Bad Request'} - statusCode 400`, 81 | }; 82 | } 83 | numbers.push(body.numbers); 84 | } 85 | 86 | if (Array.isArray(body.numbers)) { 87 | const values: string[] = body.numbers; 88 | if (values.length === 0 || [...new Set(values)].length !== values.length) { 89 | throw { 90 | httpCode: '400', 91 | context: 'Bad Request', 92 | cause: { 93 | error: [ 94 | 'listPhoneNumbers must not be an empty list', 95 | 'listPhoneNumbers must have unique values', 96 | ], 97 | message: 'Check the type of properties and values entered', 98 | }, 99 | node, 100 | workflow: this.getWorkflow(), 101 | message: `${'Bad Request'} - statusCode 400`, 102 | }; 103 | } 104 | 105 | values.map((v, index) => { 106 | if (!v.match(/\d+/g)) { 107 | throw { 108 | httpCode: '400', 109 | context: 'Bad Request', 110 | cause: { 111 | error: `listPhoneNumbers[${index}] must be a numeric string`, 112 | message: 'Check the type of properties and values entered', 113 | }, 114 | node, 115 | workflow: this.getWorkflow(), 116 | message: `${'Bad Request'} - statusCode 400`, 117 | }; 118 | } 119 | 120 | numbers.push(v); 121 | }); 122 | } 123 | Object.assign(requestOptions.body as {}, { numbers: [...numbers] }); 124 | 125 | return requestOptions; 126 | } 127 | 128 | export async function prepareShippingOptions( 129 | this: IExecuteSingleFunctions, 130 | requestOptions: IHttpRequestOptions, 131 | ): Promise { 132 | const body = requestOptions.body as RequestBody.IOptions; 133 | 134 | if(body?.options?.mentions?.everyone === 'everyone') { 135 | body.options.mentions.everyone = body.options.mentions.everyone === 'everyone'; 136 | } 137 | 138 | const opts: { [key: string]: {} | number | string[] } = {}; 139 | if (body?.options) { 140 | for (const [key, value] of Object.entries(body.options)) { 141 | if (isNotempty(value as string)) { 142 | if (key === 'quoted') { 143 | opts[key] = { messageId: value as string }; 144 | continue; 145 | } 146 | opts[key] = value; 147 | } 148 | } 149 | } 150 | 151 | const optsKeys = Object.keys(opts); 152 | 153 | Object.assign(requestOptions.body as {}, { options: optsKeys.length > 0 ? opts : undefined }); 154 | 155 | return requestOptions; 156 | } 157 | 158 | export async function sendButtonsMessage( 159 | this: IExecuteSingleFunctions, 160 | requestOptions: IHttpRequestOptions, 161 | ): Promise { 162 | const body = requestOptions.body as RequestBody.IButtonsMessage; 163 | 164 | if (body?.mediaData) { 165 | if (body.mediaData?.type && body.mediaData?.source) { 166 | body.buttonsMessage.mediaMessage = { 167 | mediaType: body.mediaData.type, 168 | url: body.mediaData.source, 169 | }; 170 | } 171 | } 172 | 173 | const buttonFieldTypeProperty = this.getNodeParameter('buttonFieldTypeProperty'); 174 | if (buttonFieldTypeProperty === 'collection') { 175 | body.buttonsMessage.buttons = body.buttons!.replyButtons; 176 | } 177 | 178 | delete body.buttons; 179 | delete body.mediaData; 180 | 181 | Object.assign(requestOptions.body as {}, { ...body }); 182 | 183 | return requestOptions; 184 | } 185 | 186 | export async function sendTemplateMessage( 187 | this: IExecuteSingleFunctions, 188 | requestOptions: IHttpRequestOptions, 189 | ): Promise { 190 | const body = requestOptions.body as RequestBody.ITemplateMessage; 191 | 192 | if (body?.mediaData) { 193 | if (body.mediaData?.type && body.mediaData?.source) { 194 | body.templateMessage.mediaMessage = { 195 | mediaType: body.mediaData?.type, 196 | url: body.mediaData?.source, 197 | }; 198 | } 199 | } 200 | 201 | const templateFieldTypeProperty = this.getNodeParameter('templateFieldTypeProperty'); 202 | if (templateFieldTypeProperty === 'collection') { 203 | body.templateMessage.buttons = body.buttons!.templateButtons; 204 | } 205 | 206 | delete body.buttons; 207 | delete body.mediaData; 208 | 209 | Object.assign(requestOptions.body as {}, { ...body }); 210 | 211 | return requestOptions; 212 | } 213 | 214 | export async function sendListMessage( 215 | this: IExecuteSingleFunctions, 216 | requestOptions: IHttpRequestOptions, 217 | ): Promise { 218 | const body = requestOptions.body as RequestBody.IListMessage; 219 | 220 | const listFieldTypeProperty = this.getNodeParameter('listFieldTypeProperty'); 221 | 222 | let sections: RequestBody.Isection[]; 223 | 224 | if (listFieldTypeProperty === 'collection') { 225 | const listMessage = { 226 | title: body.listMessage.title, 227 | description: body.listMessage.description, 228 | footerText: body.listMessage.footerText, 229 | buttonText: body.listMessage.buttonText, 230 | sections: (body.listMessage.sections as RequestBody.Isection[]).map((section) => { 231 | return { 232 | title: section.title, 233 | rows: section.rowsProperty!.rows, 234 | }; 235 | }), 236 | }; 237 | 238 | sections = listMessage.sections; 239 | 240 | Object.assign(requestOptions.body as {}, { listMessage }); 241 | } else { 242 | sections = body.listMessage?.sections; 243 | } 244 | 245 | if ( 246 | !Array.isArray(sections) || 247 | sections.length === 0 || 248 | sections.length !== [...new Set(sections)].length 249 | ) { 250 | throw new NodeApiError( 251 | this.getNode(), 252 | { error: ['List items must not be empty', 'List items must be unique'] }, 253 | { message: 'Bad Request', description: 'check properties', httpCode: '400' }, 254 | ); 255 | } 256 | 257 | sections.forEach((section, index) => { 258 | if (!section?.title) { 259 | throw new NodeApiError( 260 | this.getNode(), 261 | { error: `Section[${index}].title is empty` }, 262 | { message: 'Dad Request', description: 'Title cannot be empty', httpCode: '400' }, 263 | ); 264 | } 265 | 266 | if ( 267 | !Array.isArray(section?.rows) || 268 | !section.rows?.length || 269 | section.rows.length === 0 || 270 | [...new Set(section.rows)].length !== section.rows.length 271 | ) { 272 | throw new NodeApiError( 273 | this.getNode(), 274 | { error: 'Empty list items' }, 275 | { message: 'Bad Request', description: 'List items cannot be empty', httpCode: '400' }, 276 | ); 277 | } 278 | 279 | if ([...new Set(section.rows)].length !== section.rows.length) { 280 | throw new NodeApiError( 281 | this.getNode(), 282 | { error: ['List items must not be empty', 'List items must be unique'] }, 283 | { message: 'Bad Request', description: 'check properties', httpCode: '400' }, 284 | ); 285 | } 286 | }); 287 | 288 | return requestOptions; 289 | } 290 | 291 | export async function sendContactMessage( 292 | this: IExecuteSingleFunctions, 293 | requestOptions: IHttpRequestOptions, 294 | ): Promise { 295 | const body = requestOptions.body as RequestBody.IContactMessage; 296 | 297 | const contactTypeProperty = this.getNodeParameter('contactTypeProperty'); 298 | if (contactTypeProperty === 'collection') { 299 | Object.assign(requestOptions.body as {}, { 300 | contactsMessage: [...body.contactMessage.contacts], 301 | }); 302 | } 303 | 304 | return requestOptions; 305 | } 306 | 307 | export async function readMessage( 308 | this: IExecuteSingleFunctions, 309 | requestOptions: IHttpRequestOptions, 310 | ): Promise { 311 | const body = requestOptions.body as RequestBody.IReadMessage; 312 | if (!Array.isArray(body?.readMessage)) { 313 | throw new NodeApiError( 314 | this.getNode(), 315 | { error: 'readMessagesProperty must be an array' }, 316 | { message: 'Bad request', description: 'check properties', httpCode: '400' }, 317 | ); 318 | } 319 | 320 | requestOptions.body = [...body.readMessage]; 321 | 322 | return requestOptions; 323 | } 324 | 325 | export async function createGroup( 326 | this: IExecuteSingleFunctions, 327 | requestOptions: IHttpRequestOptions, 328 | ): Promise { 329 | const body = requestOptions.body as RequestBody.ICreateGroup; 330 | 331 | if (!Array.isArray(body.participants)) { 332 | body.participants = [...body.participants.replace(/[' ']+/gm, '').split(/,/)]; 333 | } 334 | 335 | const descriptionGruop = this.getNodeParameter( 336 | 'descriptionProperty.groupDescription.description', 337 | ); 338 | const profilePicture = this.getNodeParameter( 339 | 'profilePictureProperty.profilePictureGroup.profilePicture', 340 | ); 341 | 342 | Object.assign(requestOptions.body as {}, { descriptionGruop, profilePicture }); 343 | 344 | return requestOptions; 345 | } 346 | 347 | export async function updateGroupIngo( 348 | this: IExecuteSingleFunctions, 349 | requestOptions: IHttpRequestOptions, 350 | ): Promise { 351 | const body = requestOptions.body as {}; 352 | 353 | const keys = Object.keys(body) || []; 354 | if (keys.length === 0) { 355 | throw new NodeApiError( 356 | this.getNode(), 357 | { error: ['The Subject and Description properties are missing', 'Report at least one'] }, 358 | { message: 'Bad Request', description: 'Properties not found', httpCode: '400' }, 359 | ); 360 | } 361 | 362 | return requestOptions; 363 | } 364 | -------------------------------------------------------------------------------- /nodes/CodeChat/descriptions/Chat.desc.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import { formatNumber, readMessage, requestURL } from '../Generic.func'; 3 | 4 | export const onWhatsappProperties: INodeProperties[] = [ 5 | { 6 | displayName: 'List Recipient Phone Numbers', 7 | name: 'listPhoneNumbers', 8 | type: 'json', 9 | default: [], 10 | placeholder: `[Array:['5531900000000, '5521911111111']] or 5531922222222`, 11 | description: 'This field supports both a list and a single number', 12 | hint: 'Check if the contact is a whatsapp contact. When entering a phone number, make sure to include the country code', 13 | routing: { 14 | send: { type: 'body', property: 'numbers', preSend: [formatNumber] }, 15 | request: { 16 | url: '=' + requestURL('chat', 'onWhatsApp'), 17 | method: 'POST', 18 | }, 19 | }, 20 | displayOptions: { 21 | show: { 22 | resource: ['chat'], 23 | operation: ['onWhatsApp'], 24 | }, 25 | }, 26 | }, 27 | ]; 28 | 29 | export const updatePresence: INodeProperties[] = [ 30 | { 31 | displayName: 'Recipient Phone Numbers', 32 | name: 'numberProperty', 33 | required: true, 34 | type: 'string', 35 | default: '', 36 | routing: { send: { type: 'query', property: 'number' } }, 37 | displayOptions: { 38 | show: { 39 | resource: ['chat'], 40 | operation: ['updatePresence'], 41 | }, 42 | }, 43 | }, 44 | 45 | { 46 | displayName: 'Precense Type', 47 | name: 'presenceTypeProperty', 48 | required: true, 49 | description: 'Simulate your presence in the chat', 50 | type: 'options', 51 | options: [ 52 | { 53 | name: 'Available', 54 | value: 'available', 55 | }, 56 | { 57 | name: 'Composing', 58 | value: 'composing', 59 | }, 60 | { 61 | name: 'Paused', 62 | value: 'paused', 63 | }, 64 | { 65 | name: 'Recording', 66 | value: 'recording', 67 | }, 68 | { 69 | name: 'Unavailable', 70 | value: 'unavailable', 71 | }, 72 | ], 73 | default: 'available', 74 | routing: { send: { type: 'body', property: 'presence' } }, 75 | displayOptions: { 76 | show: { 77 | resource: ['chat'], 78 | operation: ['updatePresence'], 79 | }, 80 | }, 81 | }, 82 | 83 | { 84 | displayName: 'Delay', 85 | name: 'delayProperty', 86 | required: true, 87 | type: 'number', 88 | default: 1200, 89 | routing: { send: { type: 'body', property: 'delay' } }, 90 | displayOptions: { 91 | show: { 92 | resource: ['chat'], 93 | operation: ['updatePresence'], 94 | }, 95 | }, 96 | }, 97 | 98 | { 99 | displayName: 'Set Routing', 100 | name: 'setRouting', 101 | type: 'hidden', 102 | default: '', 103 | routing: { 104 | request: { 105 | url: '=' + requestURL('chat', 'updatePresence'), 106 | method: 'PUT', 107 | }, 108 | }, 109 | displayOptions: { 110 | show: { 111 | resource: ['chat'], 112 | operation: ['updatePresence'], 113 | }, 114 | }, 115 | }, 116 | ]; 117 | 118 | export const markMSGReadProperties: INodeProperties[] = [ 119 | { 120 | displayName: 'Mark Message as Read', 121 | name: 'readMessagesProperty', 122 | required: true, 123 | type: 'json', 124 | default: [], 125 | placeholder: `[Array:[messageId:'id',wuid:'123@s.whatsapp.net',fromMe:false]]`, 126 | routing: { 127 | send: { type: 'body', property: 'readMessage', preSend: [readMessage] }, 128 | request: { 129 | url: '=' + requestURL('chat', 'markMessageAsRead'), 130 | method: 'PUT', 131 | }, 132 | }, 133 | displayOptions: { 134 | show: { 135 | resource: ['chat'], 136 | operation: ['markMessageAsRead'], 137 | }, 138 | }, 139 | }, 140 | ]; 141 | 142 | export const deleteMessageProperties: INodeProperties[] = [ 143 | { 144 | displayName: 'Message ID', 145 | name: 'messgeIdProperty', 146 | required: true, 147 | type: 'string', 148 | default: '', 149 | placeholder: 'E10D3435A07F6111089C7875814A667F', 150 | routing: { send: { type: 'body', property: 'messageId' } }, 151 | displayOptions: { 152 | show: { 153 | resource: ['chat'], 154 | operation: ['deleteMessage'], 155 | }, 156 | }, 157 | }, 158 | 159 | { 160 | displayName: 'De', 161 | name: 'fromMeProperty', 162 | required: true, 163 | type: 'boolean', 164 | default: true, 165 | placeholder: '', 166 | routing: { send: { type: 'body', property: 'fromMe' } }, 167 | displayOptions: { 168 | show: { 169 | resource: ['chat'], 170 | operation: ['deleteMessage'], 171 | }, 172 | }, 173 | }, 174 | 175 | { 176 | displayName: 'Wuid', 177 | name: 'wuidProperty', 178 | required: true, 179 | type: 'string', 180 | default: '', 181 | placeholder: '5531900000000@s.whatsapp.net', 182 | routing: { send: { type: 'body', property: 'wuid' } }, 183 | displayOptions: { 184 | show: { 185 | resource: ['chat'], 186 | operation: ['deleteMessage'], 187 | }, 188 | }, 189 | }, 190 | 191 | { 192 | displayName: 'Participant', 193 | name: 'participantProperty', 194 | type: 'string', 195 | default: '', 196 | placeholder: '5531900000000@s.whatsapp.net', 197 | description: 'Pass this property if it is a group message', 198 | routing: { send: { type: 'body', property: 'participant' } }, 199 | displayOptions: { 200 | show: { 201 | resource: ['chat'], 202 | operation: ['deleteMessage'], 203 | }, 204 | }, 205 | }, 206 | 207 | { 208 | displayName: 'Set Routing', 209 | name: 'setRouting', 210 | type: 'hidden', 211 | default: '', 212 | routing: { 213 | request: { 214 | url: '=' + requestURL('chat', 'deleteMessage'), 215 | method: 'DELETE', 216 | }, 217 | }, 218 | displayOptions: { 219 | show: { 220 | resource: ['chat'], 221 | operation: ['deleteMessage'], 222 | }, 223 | }, 224 | }, 225 | ]; 226 | 227 | export const blockCobtactProperties: INodeProperties[] = [ 228 | { 229 | displayName: 'Phone Number', 230 | name: 'numberProperty', 231 | required: true, 232 | type: 'string', 233 | default: '', 234 | routing: { send: { type: 'query', property: 'number' } }, 235 | displayOptions: { 236 | show: { 237 | resource: ['chat'], 238 | operation: ['blockContact'], 239 | }, 240 | }, 241 | }, 242 | 243 | { 244 | displayName: 'Action', 245 | name: 'actionProperty', 246 | required: true, 247 | type: 'options', 248 | options: [ 249 | { name: 'Block', value: 'block' }, 250 | { name: 'Unblock', value: 'unblock' }, 251 | ], 252 | default: 'block', 253 | routing: { send: { type: 'body', property: 'action' } }, 254 | displayOptions: { 255 | show: { 256 | resource: ['chat'], 257 | operation: ['blockContact'], 258 | }, 259 | }, 260 | }, 261 | 262 | { 263 | displayName: 'Set Routing', 264 | name: 'setRouting', 265 | type: 'hidden', 266 | default: '', 267 | routing: { 268 | request: { 269 | url: '=' + requestURL('chat', 'blockContact'), 270 | method: 'PUT', 271 | }, 272 | }, 273 | displayOptions: { 274 | show: { 275 | resource: ['chat'], 276 | operation: ['blockContact'], 277 | }, 278 | }, 279 | }, 280 | ]; 281 | 282 | export const statusContactPorperties: INodeProperties[] = [ 283 | { 284 | displayName: 'Recipient Phone Numbers', 285 | name: 'numberProperty', 286 | required: true, 287 | type: 'string', 288 | default: '', 289 | routing: { 290 | send: { type: 'query', property: 'number' }, 291 | request: { 292 | url: '=' + requestURL('chat', 'fetchStatusContact'), 293 | method: 'GET', 294 | }, 295 | }, 296 | displayOptions: { 297 | show: { 298 | resource: ['chat'], 299 | operation: ['contactStatus'], 300 | }, 301 | }, 302 | }, 303 | ]; 304 | 305 | export const updateStatusProperties: INodeProperties[] = [ 306 | { 307 | displayName: 'Status', 308 | name: 'StatusProperty', 309 | required: true, 310 | type: 'string', 311 | description: 'Update the status of the logged in number', 312 | default: '', 313 | routing: { 314 | send: { type: 'body', property: 'status' }, 315 | request: { 316 | url: '=' + requestURL('chat', 'updateStaus'), 317 | method: 'PUT', 318 | }, 319 | }, 320 | displayOptions: { 321 | show: { 322 | resource: ['chat'], 323 | operation: ['updateStatus'], 324 | }, 325 | }, 326 | }, 327 | ]; 328 | 329 | export const budinessProfileProperties: INodeProperties[] = [ 330 | { 331 | displayName: 'Phone Number', 332 | name: 'numberProperty', 333 | required: true, 334 | type: 'string', 335 | default: '', 336 | description: "Retrieve a contact's business information", 337 | placeholder: '5531900000000', 338 | routing: { 339 | send: { type: 'query', property: 'number' }, 340 | request: { 341 | url: '=' + requestURL('chat', 'fetchBusinessProfile'), 342 | method: 'GET', 343 | }, 344 | }, 345 | displayOptions: { 346 | show: { 347 | resource: ['chat'], 348 | operation: ['businesProfile'], 349 | }, 350 | }, 351 | }, 352 | ]; 353 | 354 | export const profilePictureProperties: INodeProperties[] = [ 355 | { 356 | displayName: 'Pnhone Number', 357 | name: 'numberProperty', 358 | description: 'Retrieve profile picture URL of some contact', 359 | required: true, 360 | type: 'string', 361 | default: '', 362 | placeholder: '5531900000000', 363 | routing: { 364 | send: { type: 'query', property: 'number' }, 365 | request: { 366 | url: '=' + requestURL('chat', 'fetchProfilePictureUrl'), 367 | method: 'GET', 368 | }, 369 | }, 370 | displayOptions: { 371 | show: { 372 | resource: ['chat'], 373 | operation: ['profilePictureUrl'], 374 | }, 375 | }, 376 | }, 377 | ]; 378 | -------------------------------------------------------------------------------- /nodes/CodeChat/descriptions/Group.desc.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import { createGroup, requestURL, updateGroupIngo } from '../Generic.func'; 3 | 4 | export const createGroupPrperties: INodeProperties[] = [ 5 | { 6 | displayName: 'Subject', 7 | name: 'subjectProperties', 8 | required: true, 9 | type: 'string', 10 | default: '', 11 | displayOptions: { 12 | show: { 13 | resource: ['group'], 14 | operation: ['createGroup'], 15 | }, 16 | }, 17 | routing: { send: { type: 'body', property: 'groupSubject' } }, 18 | }, 19 | 20 | { 21 | displayName: 'Participants List', 22 | name: 'participantsList', 23 | type: 'json', 24 | default: [], 25 | placeholder: `[Array:['5531900000000, '5521911111111']] or 5531922222222`, 26 | description: 'This field supports a list, or a single number, or comma-separated numbers', 27 | hint: 'When entering a phone number, make sure to include the country code', 28 | routing: { send: { type: 'body', property: 'participants' } }, 29 | displayOptions: { 30 | show: { 31 | resource: ['group'], 32 | operation: ['createGroup'], 33 | }, 34 | }, 35 | }, 36 | 37 | { 38 | displayName: 'Description Group', 39 | name: 'descriptionProperty', 40 | placeholder: 'Add Description', 41 | type: 'fixedCollection', 42 | default: {}, 43 | options: [ 44 | { 45 | displayName: 'Description', 46 | name: 'groupDescription', 47 | values: [ 48 | { 49 | displayName: 'Description', 50 | name: 'description', 51 | type: 'string', 52 | default: '', 53 | }, 54 | ], 55 | }, 56 | ], 57 | displayOptions: { 58 | show: { 59 | resource: ['group'], 60 | operation: ['createGroup'], 61 | }, 62 | }, 63 | }, 64 | 65 | { 66 | displayName: 'Group Profile Picture', 67 | name: 'profilePictureProperty', 68 | placeholder: 'Add Picture', 69 | type: 'fixedCollection', 70 | default: {}, 71 | options: [ 72 | { 73 | displayName: 'Profile Picture', 74 | name: 'profilePictureGroup', 75 | values: [ 76 | { 77 | displayName: 'Profile Picture', 78 | name: 'profilePicture', 79 | type: 'string', 80 | default: '', 81 | placeholder: 'url or base64', 82 | }, 83 | ], 84 | }, 85 | ], 86 | displayOptions: { 87 | show: { 88 | resource: ['group'], 89 | operation: ['createGroup'], 90 | }, 91 | }, 92 | }, 93 | 94 | { 95 | displayName: 'Sert Routing', 96 | name: 'setRouting', 97 | type: 'hidden', 98 | default: '', 99 | displayOptions: { 100 | show: { 101 | resource: ['group'], 102 | operation: ['createGroup'], 103 | }, 104 | }, 105 | routing: { 106 | send: { preSend: [createGroup] }, 107 | request: { 108 | url: '=' + requestURL('group', 'create'), 109 | method: 'POST', 110 | }, 111 | }, 112 | }, 113 | ]; 114 | 115 | export const groupInviteCodeProperties: INodeProperties[] = [ 116 | { 117 | displayName: 'Group ID', 118 | name: 'groupIdProperty', 119 | required: true, 120 | placeholder: '123456789-123345@g.us', 121 | type: 'string', 122 | default: '', 123 | description: 'Retrieve the group invite you created', 124 | displayOptions: { 125 | show: { 126 | resource: ['group'], 127 | operation: ['inviteCode'], 128 | }, 129 | }, 130 | routing: { 131 | send: { type: 'query', property: 'groupJid' }, 132 | request: { 133 | url: '=' + requestURL('group', 'invitionCode'), 134 | method: 'GET', 135 | }, 136 | }, 137 | }, 138 | ]; 139 | 140 | export const acceptInviteCodeProperties: INodeProperties[] = [ 141 | { 142 | displayName: 'Group ID', 143 | name: 'groupIdProperty', 144 | required: true, 145 | placeholder: '123456789-123345@g.us', 146 | type: 'string', 147 | default: '', 148 | description: 'Group ID you want to accept the invitation', 149 | displayOptions: { 150 | show: { 151 | resource: ['group'], 152 | operation: ['acceptInvite'], 153 | }, 154 | }, 155 | routing: { 156 | send: { type: 'query', property: 'groupJid' }, 157 | request: { 158 | url: '=' + requestURL('group', 'acceptInviteCode'), 159 | method: 'GET', 160 | }, 161 | }, 162 | }, 163 | ]; 164 | 165 | export const revokeInviteCodeProperties: INodeProperties[] = [ 166 | { 167 | displayName: 'Group ID', 168 | name: 'groupIdProperty', 169 | required: true, 170 | placeholder: '123456789-123345@g.us', 171 | type: 'string', 172 | default: '', 173 | description: 'Group ID you want to revoke the invitation', 174 | displayOptions: { 175 | show: { 176 | resource: ['group'], 177 | operation: ['revokeInvite'], 178 | }, 179 | }, 180 | routing: { 181 | send: { type: 'query', property: 'groupJid' }, 182 | request: { 183 | url: '=' + requestURL('group', 'revokeInviteCode'), 184 | method: 'GET', 185 | }, 186 | }, 187 | }, 188 | ]; 189 | 190 | export const updateGroupProperties: INodeProperties[] = [ 191 | { 192 | displayName: 'Group ID', 193 | name: 'groupIdProperty', 194 | required: true, 195 | placeholder: '123456789-123345@g.us', 196 | type: 'string', 197 | default: '', 198 | description: 'Group ID you want to update', 199 | displayOptions: { 200 | show: { 201 | resource: ['group'], 202 | operation: ['updateInfo'], 203 | }, 204 | }, 205 | routing: { send: { type: 'query', property: 'groupJid' } }, 206 | }, 207 | 208 | { 209 | displayName: 'Subject', 210 | name: 'subjectProperty', 211 | type: 'string', 212 | default: '', 213 | description: 'Update group subject', 214 | displayOptions: { 215 | show: { 216 | resource: ['group'], 217 | operation: ['updateInfo'], 218 | }, 219 | }, 220 | routing: { send: { type: 'body', property: 'groupSubject' } }, 221 | }, 222 | 223 | { 224 | displayName: 'Description', 225 | name: 'descriptionProperty', 226 | type: 'string', 227 | default: '', 228 | description: 'Update group description', 229 | displayOptions: { 230 | show: { 231 | resource: ['group'], 232 | operation: ['updateInfo'], 233 | }, 234 | }, 235 | routing: { send: { type: 'body', property: 'groupDescription' } }, 236 | }, 237 | 238 | { 239 | displayName: 'Sert Routing', 240 | name: 'setRouting', 241 | type: 'hidden', 242 | default: '', 243 | displayOptions: { 244 | show: { 245 | resource: ['group'], 246 | operation: ['updateInfo'], 247 | }, 248 | }, 249 | routing: { 250 | send: { preSend: [updateGroupIngo] }, 251 | request: { 252 | url: '=' + requestURL('group', 'updateInfo'), 253 | method: 'PUT', 254 | }, 255 | }, 256 | }, 257 | ]; 258 | 259 | export const updatePropfilePictureProperties: INodeProperties[] = [ 260 | { 261 | displayName: 'Group ID', 262 | name: 'groupIdProperty', 263 | required: true, 264 | placeholder: '123456789-123345@g.us', 265 | type: 'string', 266 | default: '', 267 | description: 'Group ID you want to update', 268 | displayOptions: { 269 | show: { 270 | resource: ['group'], 271 | operation: ['updatePicture'], 272 | }, 273 | }, 274 | routing: { send: { type: 'query', property: 'groupJid' } }, 275 | }, 276 | 277 | { 278 | displayName: 'Profile Picture', 279 | name: 'profilePictureProperty', 280 | required: true, 281 | placeholder: 'url or base64', 282 | type: 'string', 283 | default: '', 284 | displayOptions: { 285 | show: { 286 | resource: ['group'], 287 | operation: ['updatePicture'], 288 | }, 289 | }, 290 | routing: { send: { type: 'body', property: 'urlOrBse64' } }, 291 | }, 292 | 293 | { 294 | displayName: 'Sert Routing', 295 | name: 'setRouting', 296 | type: 'hidden', 297 | default: '', 298 | displayOptions: { 299 | show: { 300 | resource: ['group'], 301 | operation: ['updatePicture'], 302 | }, 303 | }, 304 | routing: { 305 | request: { 306 | url: '=' + requestURL('group', 'updateProfilePicture'), 307 | method: 'PUT', 308 | }, 309 | }, 310 | }, 311 | ]; 312 | 313 | export const groupMetadataProperties: INodeProperties[] = [ 314 | { 315 | displayName: 'Group ID', 316 | name: 'groupIdProperty', 317 | required: true, 318 | placeholder: '123456789-123345@g.us', 319 | type: 'string', 320 | default: '', 321 | description: 'Retrieve group information', 322 | displayOptions: { 323 | show: { 324 | resource: ['group'], 325 | operation: ['groupMetadata'], 326 | }, 327 | }, 328 | routing: { 329 | send: { type: 'query', property: 'groupJid' }, 330 | request: { 331 | url: '=' + requestURL('group', 'fetchInfo'), 332 | method: 'GET', 333 | }, 334 | }, 335 | }, 336 | ]; 337 | 338 | export const updateParticipantsPorperties: INodeProperties[] = [ 339 | { 340 | displayName: 'Group ID', 341 | name: 'groupIdProperty', 342 | required: true, 343 | placeholder: '123456789-123345@g.us', 344 | type: 'string', 345 | default: '', 346 | description: 'Group ID created by the number logged in', 347 | displayOptions: { 348 | show: { 349 | resource: ['group'], 350 | operation: ['updateParticipants'], 351 | }, 352 | }, 353 | routing: { 354 | send: { type: 'query', property: 'groupJid' }, 355 | }, 356 | }, 357 | 358 | { 359 | displayName: 'Action', 360 | name: 'actionProperty', 361 | required: true, 362 | type: 'options', 363 | options: [ 364 | { name: 'Add', value: 'add' }, 365 | { name: 'Remove', value: 'remove' }, 366 | { name: 'Promote Admin', value: 'promote' }, 367 | { name: 'Demote', value: 'demote' }, 368 | ], 369 | default: 'add', 370 | displayOptions: { 371 | show: { 372 | resource: ['group'], 373 | operation: ['updateParticipants'], 374 | }, 375 | }, 376 | routing: { send: { type: 'body', property: 'action' } }, 377 | }, 378 | 379 | { 380 | displayName: 'Sert Routing', 381 | name: 'setRouting', 382 | type: 'hidden', 383 | default: '', 384 | displayOptions: { 385 | show: { 386 | resource: ['group'], 387 | operation: ['updateParticipants'], 388 | }, 389 | }, 390 | routing: { 391 | request: { 392 | url: '=' + requestURL('group', 'updateParticipants'), 393 | method: 'PUT', 394 | }, 395 | }, 396 | }, 397 | ]; 398 | 399 | export const updateRuleGroupProperties: INodeProperties[] = [ 400 | { 401 | displayName: 'Group ID', 402 | name: 'groupIdProperty', 403 | required: true, 404 | placeholder: '123456789-123345@g.us', 405 | type: 'string', 406 | default: '', 407 | description: 'Apply rules to the group', 408 | displayOptions: { 409 | show: { 410 | resource: ['group'], 411 | operation: ['updateParticipants'], 412 | }, 413 | }, 414 | routing: { 415 | send: { type: 'query', property: 'groupJid' }, 416 | }, 417 | }, 418 | 419 | { 420 | displayName: 'Rules', 421 | name: 'ruleProperty', 422 | required: true, 423 | type: 'options', 424 | options: [ 425 | { name: 'Comments: Admins', value: 'announcement' }, 426 | { name: 'Comments: All', value: 'not_announcement' }, 427 | { name: 'Edit Group: Admins', value: 'locked' }, 428 | { name: 'Edit Group: All', value: 'unLocked' }, 429 | ], 430 | default: 'announcement', 431 | displayOptions: { 432 | show: { 433 | resource: ['group'], 434 | operation: ['updateRule'], 435 | }, 436 | }, 437 | routing: { 438 | send: { type: 'body', property: 'updateRule' }, 439 | }, 440 | }, 441 | 442 | { 443 | displayName: 'Sert Routing', 444 | name: 'setRouting', 445 | type: 'hidden', 446 | default: '', 447 | displayOptions: { 448 | show: { 449 | resource: ['group'], 450 | operation: ['updateRule'], 451 | }, 452 | }, 453 | routing: { 454 | request: { 455 | url: '=' + requestURL('group', 'updateRule'), 456 | method: 'PUT', 457 | }, 458 | }, 459 | }, 460 | ]; 461 | 462 | export const changeExpirationProperties: INodeProperties[] = [ 463 | { 464 | displayName: 'Group ID', 465 | name: 'groupIdProperty', 466 | required: true, 467 | placeholder: '123456789-123345@g.us', 468 | type: 'string', 469 | default: '', 470 | description: 'Duration of group messages', 471 | displayOptions: { 472 | show: { 473 | resource: ['group'], 474 | operation: ['changeExpiration'], 475 | }, 476 | }, 477 | routing: { 478 | send: { type: 'query', property: 'groupJid' }, 479 | }, 480 | }, 481 | 482 | { 483 | displayName: 'Rules', 484 | name: 'ruleProperty', 485 | required: true, 486 | type: 'options', 487 | options: [ 488 | { name: 'No Expiration', value: 0 }, 489 | { name: 'One Day', value: 1 }, 490 | { name: 'One Week', value: 7 }, 491 | { name: 'Quarterly', value: 90 }, 492 | ], 493 | default: 0, 494 | displayOptions: { 495 | show: { 496 | resource: ['group'], 497 | operation: ['changeExpiration'], 498 | }, 499 | }, 500 | routing: { 501 | send: { type: 'body', property: 'updateRule' }, 502 | }, 503 | }, 504 | 505 | { 506 | displayName: 'Sert Routing', 507 | name: 'setRouting', 508 | type: 'hidden', 509 | default: '', 510 | displayOptions: { 511 | show: { 512 | resource: ['group'], 513 | operation: ['changeExpiration'], 514 | }, 515 | }, 516 | routing: { 517 | request: { 518 | url: '=' + requestURL('group', 'changeExpiration'), 519 | method: 'PUT', 520 | }, 521 | }, 522 | }, 523 | ]; 524 | 525 | export const fetchParticipantsProperties: INodeProperties[] = [ 526 | { 527 | displayName: 'Group ID', 528 | name: 'groupIdProperty', 529 | required: true, 530 | placeholder: '123456789-123345@g.us', 531 | type: 'string', 532 | default: '', 533 | description: 'Search for members of a group you belong to', 534 | displayOptions: { 535 | show: { 536 | resource: ['group'], 537 | operation: ['fetchParticpants'], 538 | }, 539 | }, 540 | routing: { 541 | send: { type: 'query', property: 'groupJid' }, 542 | }, 543 | }, 544 | 545 | { 546 | displayName: 'Member Type', 547 | name: 'memberTypeProperty', 548 | required: true, 549 | type: 'options', 550 | options: [ 551 | { name: 'All', value: 'all' }, 552 | { name: 'Common', value: 'common' }, 553 | { name: 'Admin', value: 'admin' }, 554 | { name: 'Super Admin', value: 'superadmin' }, 555 | ], 556 | default: 'all', 557 | displayOptions: { 558 | show: { 559 | resource: ['group'], 560 | operation: ['fetchParticpants'], 561 | }, 562 | }, 563 | routing: { 564 | send: { type: 'query', property: 'memberType' }, 565 | }, 566 | }, 567 | 568 | { 569 | displayName: 'Sert Routing', 570 | name: 'setRouting', 571 | type: 'hidden', 572 | default: '', 573 | displayOptions: { 574 | show: { 575 | resource: ['group'], 576 | operation: ['fetchParticpants'], 577 | }, 578 | }, 579 | routing: { 580 | request: { 581 | url: '=' + requestURL('group', 'fetchParticipants'), 582 | method: 'GET', 583 | }, 584 | }, 585 | }, 586 | ]; 587 | 588 | export const leaveGroupProperties: INodeProperties[] = [ 589 | { 590 | displayName: 'Group ID', 591 | name: 'groupIdProperty', 592 | required: true, 593 | placeholder: '123456789-123345@g.us', 594 | type: 'string', 595 | default: '', 596 | description: 'Group ID you want to leave', 597 | displayOptions: { 598 | show: { 599 | resource: ['group'], 600 | operation: ['leaveGroup'], 601 | }, 602 | }, 603 | routing: { 604 | send: { type: 'query', property: 'groupJid' }, 605 | request: { 606 | url: '=' + requestURL('group', 'leave'), 607 | method: 'DELETE', 608 | }, 609 | }, 610 | }, 611 | ]; 612 | -------------------------------------------------------------------------------- /nodes/CodeChat/descriptions/SendMessage.desc.ts: -------------------------------------------------------------------------------- 1 | import { INodeProperties } from 'n8n-workflow'; 2 | import { 3 | prepareShippingOptions, 4 | requestURL, 5 | sendButtonsMessage, 6 | sendContactMessage, 7 | sendListMessage, 8 | sendTemplateMessage, 9 | } from '../Generic.func'; 10 | 11 | export const optionsProperties: INodeProperties[] = [ 12 | { 13 | displayName: 'Message Options', 14 | name: 'messageOptionsProperty', 15 | type: 'fixedCollection', 16 | default: {}, 17 | typeOptions: { multipleValues: false }, 18 | options: [ 19 | { 20 | displayName: 'Options', 21 | name: 'optionsProperty', 22 | values: [ 23 | { 24 | displayName: 'Quote Message', 25 | name: 'quotedProperty', 26 | default: '', 27 | hint: 'Enter the ID of the message you want to quote', 28 | placeholder: 'messageId', 29 | type: 'string', 30 | routing: { 31 | send: { type: 'body', property: 'options.quoted' }, 32 | }, 33 | }, 34 | 35 | { 36 | displayName: 'Mention Contacts', 37 | name: 'mentionsProperty', 38 | placeholder: '', 39 | type: 'options', 40 | options: [ 41 | { name: 'Everyone', value: 'everyone' }, 42 | { name: 'One by One', value: 'onebyone' }, 43 | ], 44 | default: 'onebyone', 45 | description: 'With "everyone" mention everyone in a group', 46 | routing: { 47 | send: { type: 'body', property: 'options.mentions.everyone' }, 48 | }, 49 | }, 50 | 51 | { 52 | displayName: 'Mention Contact', 53 | name: 'mentionedProperty', 54 | default: '', 55 | hint: 'Insert a list with the contact(s) of the user(s) to be mentioned.', 56 | description: 'Mentions in both group chats and simple chats', 57 | placeholder: `[Array:['5531900000000, '5521911111111']]`, 58 | type: 'json', 59 | routing: { 60 | send: { type: 'body', property: 'options.mentions.mentioned' }, 61 | }, 62 | displayOptions: { 63 | show: { 64 | mentionsProperty: ['onebyone'], 65 | }, 66 | }, 67 | }, 68 | 69 | { 70 | displayName: 'Delay Message', 71 | name: 'delayMessageProperty', 72 | default: '', 73 | description: 'Enter the delay with which each message will be delivered', 74 | placeholder: '1200 milliseconds', 75 | type: 'number', 76 | routing: { 77 | send: { type: 'body', property: 'options.delay' }, 78 | }, 79 | }, 80 | ], 81 | }, 82 | ], 83 | displayOptions: { 84 | show: { 85 | resource: ['sendMessage'], 86 | }, 87 | }, 88 | routing: { 89 | send: { preSend: [prepareShippingOptions] }, 90 | }, 91 | }, 92 | ]; 93 | 94 | export const textProperties: INodeProperties[] = [ 95 | { 96 | displayName: 'Text Message', 97 | name: 'textProperty', 98 | required: true, 99 | default: '', 100 | description: 'The body of the message (max 4096 characters)', 101 | type: 'string', 102 | displayOptions: { 103 | show: { 104 | resource: ['sendMessage'], 105 | operation: ['sendText'], 106 | }, 107 | }, 108 | routing: { 109 | send: { type: 'body', property: 'textMessage.text' }, 110 | request: { 111 | url: '=' + requestURL('message', 'sendText'), 112 | method: 'POST', 113 | }, 114 | }, 115 | }, 116 | ]; 117 | 118 | export const buttonsProperties: INodeProperties[] = [ 119 | { 120 | displayName: 'Button Title', 121 | name: 'buttonTitleProperty', 122 | required: true, 123 | default: '', 124 | hint: '', 125 | type: 'string', 126 | displayOptions: { 127 | show: { 128 | resource: ['sendMessage'], 129 | operation: ['sendButtons'], 130 | }, 131 | }, 132 | routing: { 133 | send: { type: 'body', property: 'buttonsMessage.title' }, 134 | }, 135 | }, 136 | 137 | { 138 | displayName: 'Button Description', 139 | name: 'buttonDescProperty', 140 | required: true, 141 | default: '', 142 | type: 'string', 143 | displayOptions: { 144 | show: { 145 | resource: ['sendMessage'], 146 | operation: ['sendButtons'], 147 | }, 148 | }, 149 | routing: { 150 | send: { type: 'body', property: 'buttonsMessage.description' }, 151 | }, 152 | }, 153 | 154 | { 155 | displayName: 'Button Footer Text', 156 | name: 'buttonFooterProperty', 157 | default: '', 158 | type: 'string', 159 | displayOptions: { 160 | show: { 161 | resource: ['sendMessage'], 162 | operation: ['sendButtons'], 163 | }, 164 | }, 165 | routing: { 166 | send: { type: 'body', property: 'buttonsMessage.footerText' }, 167 | }, 168 | }, 169 | 170 | { 171 | displayName: 'Button Field Type', 172 | name: 'buttonFieldTypeProperty', 173 | required: true, 174 | noDataExpression: true, 175 | placeholder: '', 176 | type: 'options', 177 | options: [ 178 | { name: 'Collection', value: 'collection' }, 179 | { name: 'JSON', value: 'json' }, 180 | ], 181 | default: 'collection', 182 | displayOptions: { 183 | show: { 184 | resource: ['sendMessage'], 185 | operation: ['sendButtons'], 186 | }, 187 | }, 188 | }, 189 | 190 | { 191 | displayName: 'Collection Field', 192 | name: 'collectionFieldProperty', 193 | required: true, 194 | placeholder: 'Add Reply Buttons', 195 | type: 'fixedCollection', 196 | default: {}, 197 | typeOptions: { multipleValues: true, maxValue: 3 }, 198 | options: [ 199 | { 200 | displayName: 'Reply Buttons', 201 | name: 'replyButtons', 202 | values: [ 203 | { 204 | displayName: 'Display Text', 205 | name: 'displayText', 206 | type: 'string', 207 | default: '', 208 | description: 'Unique text per button', 209 | required: true, 210 | }, 211 | { 212 | displayName: 'Button ID', 213 | name: 'buttonId', 214 | type: 'string', 215 | default: '', 216 | description: 'Unique ID per button', 217 | required: true, 218 | }, 219 | ], 220 | }, 221 | ], 222 | displayOptions: { 223 | show: { 224 | resource: ['sendMessage'], 225 | operation: ['sendButtons'], 226 | buttonFieldTypeProperty: ['collection'], 227 | }, 228 | }, 229 | routing: { 230 | send: { type: 'body', property: 'buttons' }, 231 | }, 232 | }, 233 | 234 | { 235 | displayName: 'JSON Field', 236 | name: 'jsonProperty', 237 | required: true, 238 | placeholder: `[Array:[{displayText: 'Button Text', buttonId: 'btnId01'}]]`, 239 | type: 'json', 240 | default: [], 241 | description: 'Map a JSON directly to this field', 242 | displayOptions: { 243 | show: { 244 | resource: ['sendMessage'], 245 | operation: ['sendButtons'], 246 | buttonFieldTypeProperty: ['json'], 247 | }, 248 | }, 249 | routing: { 250 | send: { type: 'body', property: 'buttonsMessage.buttons' }, 251 | }, 252 | }, 253 | 254 | { 255 | displayName: 'Media Message', 256 | name: 'mediaMessageProperty', 257 | placeholder: 'Add Media Message', 258 | type: 'fixedCollection', 259 | default: {}, 260 | typeOptions: { multipleValues: false }, 261 | description: 'Embed media message to button', 262 | options: [ 263 | { 264 | displayName: 'Media Message', 265 | name: 'embedMediaMessage', 266 | values: [ 267 | { 268 | displayName: 'Media Type', 269 | name: 'mediaType', 270 | required: true, 271 | type: 'options', 272 | options: [ 273 | { name: 'Image', value: 'image' }, 274 | { name: 'Document', value: 'document' }, 275 | { name: 'Video', value: 'video' }, 276 | { name: 'Sticker', value: 'sticker' }, 277 | ], 278 | default: 'image', 279 | routing: { 280 | send: { type: 'body', property: 'mediaData.type' }, 281 | }, 282 | }, 283 | { 284 | displayName: 'Media Source', 285 | name: 'mediaSource', 286 | required: true, 287 | type: 'string', 288 | default: '', 289 | placeholder: 'url or base64', 290 | routing: { 291 | send: { type: 'body', property: 'mediaData.source' }, 292 | }, 293 | }, 294 | ], 295 | }, 296 | ], 297 | displayOptions: { 298 | show: { 299 | resource: ['sendMessage'], 300 | operation: ['sendButtons'], 301 | }, 302 | }, 303 | }, 304 | 305 | { 306 | displayName: 'Set Routing', 307 | name: 'setRouting', 308 | type: 'hidden', 309 | default: '', 310 | displayOptions: { 311 | show: { 312 | resource: ['sendMessage'], 313 | operation: ['sendButtons'], 314 | }, 315 | }, 316 | routing: { 317 | request: { 318 | url: '=' + requestURL('message', 'sendButtons'), 319 | method: 'POST', 320 | }, 321 | send: { preSend: [sendButtonsMessage] }, 322 | }, 323 | }, 324 | ]; 325 | 326 | export const templateProperties: INodeProperties[] = [ 327 | { 328 | displayName: 'Template Title', 329 | name: 'templateTitleProperty', 330 | required: true, 331 | default: '', 332 | hint: '', 333 | type: 'string', 334 | displayOptions: { 335 | show: { 336 | resource: ['sendMessage'], 337 | operation: ['sendTemplate'], 338 | }, 339 | }, 340 | routing: { 341 | send: { type: 'body', property: 'templateMessage.title' }, 342 | }, 343 | }, 344 | 345 | { 346 | displayName: 'Template Description', 347 | name: 'templateDescProperty', 348 | required: true, 349 | default: '', 350 | type: 'string', 351 | displayOptions: { 352 | show: { 353 | resource: ['sendMessage'], 354 | operation: ['sendTemplate'], 355 | }, 356 | }, 357 | routing: { 358 | send: { type: 'body', property: 'templateMessage.description' }, 359 | }, 360 | }, 361 | 362 | { 363 | displayName: 'Template Footer Text', 364 | name: 'templateFooterProperty', 365 | default: '', 366 | type: 'string', 367 | displayOptions: { 368 | show: { 369 | resource: ['sendMessage'], 370 | operation: ['sendTemplate'], 371 | }, 372 | }, 373 | routing: { 374 | send: { type: 'body', property: 'templateMessage.footerText' }, 375 | }, 376 | }, 377 | 378 | { 379 | displayName: 'Template Field Type', 380 | name: 'templateFieldTypeProperty', 381 | required: true, 382 | noDataExpression: true, 383 | placeholder: '', 384 | type: 'options', 385 | options: [ 386 | { name: 'Collection', value: 'collection' }, 387 | { name: 'JSON', value: 'json' }, 388 | ], 389 | default: 'collection', 390 | displayOptions: { 391 | show: { 392 | resource: ['sendMessage'], 393 | operation: ['sendTemplate'], 394 | }, 395 | }, 396 | }, 397 | 398 | { 399 | displayName: 'Collection Field', 400 | name: 'colectionProperty', 401 | required: true, 402 | placeholder: 'Add Template Buttons', 403 | type: 'fixedCollection', 404 | default: {}, 405 | typeOptions: { multipleValues: true }, 406 | options: [ 407 | { 408 | displayName: 'Template Buttons', 409 | name: 'templateButtons', 410 | values: [ 411 | { 412 | displayName: 'Button Type', 413 | name: 'buttonType', 414 | required: true, 415 | type: 'options', 416 | options: [ 417 | { name: 'Url Button', value: 'urlButton' }, 418 | { name: 'Call Button', value: 'callButton' }, 419 | { name: 'Reply Button', value: 'replyButton' }, 420 | ], 421 | default: 'replyButton', 422 | }, 423 | { 424 | displayName: 'Display Text', 425 | name: 'displayText', 426 | required: true, 427 | type: 'string', 428 | default: '', 429 | }, 430 | { 431 | displayName: 'Payload', 432 | name: 'payload', 433 | required: true, 434 | type: 'string', 435 | default: '', 436 | }, 437 | ], 438 | }, 439 | ], 440 | displayOptions: { 441 | show: { 442 | resource: ['sendMessage'], 443 | operation: ['sendTemplate'], 444 | templateFieldTypeProperty: ['collection'], 445 | }, 446 | }, 447 | routing: { 448 | send: { type: 'body', property: 'buttons' }, 449 | }, 450 | }, 451 | 452 | { 453 | displayName: 'JSON Field', 454 | name: 'jsonProperty', 455 | required: true, 456 | placeholder: `[Array:[{buttonType: 'replyButton', displayText: 'Button Text', payload: 'btnId01'}]]`, 457 | type: 'json', 458 | default: [], 459 | description: 'Map a JSON directly to this field', 460 | displayOptions: { 461 | show: { 462 | resource: ['sendMessage'], 463 | operation: ['sendTemplate'], 464 | templateFieldTypeProperty: ['json'], 465 | }, 466 | }, 467 | routing: { 468 | send: { type: 'body', property: 'templateMessage.buttons' }, 469 | }, 470 | }, 471 | 472 | { 473 | displayName: 'Media Message', 474 | name: 'mediaMessageProperty', 475 | placeholder: 'Add Media Message', 476 | type: 'fixedCollection', 477 | default: {}, 478 | typeOptions: { multipleValues: false }, 479 | description: 'Embed media message to button', 480 | options: [ 481 | { 482 | displayName: 'Media Message', 483 | name: 'embedMediaMessage', 484 | values: [ 485 | { 486 | displayName: 'Media Type', 487 | name: 'mediaType', 488 | required: true, 489 | type: 'options', 490 | options: [ 491 | { name: 'Image', value: 'image' }, 492 | { name: 'Document', value: 'document' }, 493 | { name: 'Video', value: 'video' }, 494 | { name: 'Sticker', value: 'sticker' }, 495 | ], 496 | default: 'image', 497 | routing: { 498 | send: { type: 'body', property: 'mediaData.type' }, 499 | }, 500 | }, 501 | { 502 | displayName: 'Media Source', 503 | name: 'mediaSource', 504 | required: true, 505 | type: 'string', 506 | default: '', 507 | placeholder: 'url or base64', 508 | routing: { 509 | send: { type: 'body', property: 'mediaData.source' }, 510 | }, 511 | }, 512 | ], 513 | }, 514 | ], 515 | displayOptions: { 516 | show: { 517 | resource: ['sendMessage'], 518 | operation: ['sendTemplate'], 519 | }, 520 | }, 521 | }, 522 | 523 | { 524 | displayName: 'Set Routing', 525 | name: 'setRouting', 526 | type: 'hidden', 527 | default: '', 528 | displayOptions: { 529 | show: { 530 | resource: ['sendMessage'], 531 | operation: ['sendTemplate'], 532 | }, 533 | }, 534 | routing: { 535 | request: { 536 | url: '=' + requestURL('message', 'sendTemplate'), 537 | method: 'POST', 538 | }, 539 | send: { preSend: [sendTemplateMessage] }, 540 | }, 541 | }, 542 | ]; 543 | 544 | export const mediaMessageProperties: INodeProperties[] = [ 545 | { 546 | displayName: 'Media Type', 547 | name: 'mediaTypeProperty', 548 | required: true, 549 | placeholder: '', 550 | type: 'options', 551 | options: [ 552 | { name: 'Image', value: 'image' }, 553 | { name: 'Document', value: 'document' }, 554 | { name: 'Video', value: 'video' }, 555 | { name: 'Sticker', value: 'sticker' }, 556 | ], 557 | default: 'image', 558 | routing: { 559 | send: { type: 'body', property: 'mediaMessage.mediaType' }, 560 | }, 561 | displayOptions: { 562 | show: { 563 | resource: ['sendMessage'], 564 | operation: ['sendMedia'], 565 | }, 566 | }, 567 | }, 568 | 569 | { 570 | displayName: 'Media URL', 571 | name: 'mediaUrlProperty', 572 | required: true, 573 | type: 'string', 574 | default: '', 575 | placeholder: 'https://yourdomain.com/image.png', 576 | routing: { 577 | send: { type: 'body', property: 'mediaMessage.url' }, 578 | }, 579 | displayOptions: { 580 | show: { 581 | resource: ['sendMessage'], 582 | operation: ['sendMedia'], 583 | }, 584 | }, 585 | }, 586 | 587 | { 588 | displayName: 'Media Caption', 589 | name: 'mediaCaptionProperty', 590 | type: 'fixedCollection', 591 | typeOptions: { multipleValues: false }, 592 | default: {}, 593 | options: [ 594 | { 595 | displayName: 'Caption', 596 | name: 'captionProperty', 597 | values: [ 598 | { 599 | displayName: 'Caption', 600 | name: 'caption', 601 | type: 'string', 602 | default: '', 603 | placeholder: 'caption - description - title', 604 | routing: { 605 | send: { type: 'body', property: 'mediaMessage.caption' }, 606 | }, 607 | }, 608 | ], 609 | }, 610 | ], 611 | displayOptions: { 612 | show: { 613 | resource: ['sendMessage'], 614 | operation: ['sendMedia'], 615 | }, 616 | }, 617 | }, 618 | 619 | { 620 | displayName: 'Set Routing', 621 | name: 'setRouting', 622 | type: 'hidden', 623 | default: '', 624 | displayOptions: { 625 | show: { 626 | resource: ['sendMessage'], 627 | operation: ['sendMedia'], 628 | }, 629 | }, 630 | routing: { 631 | request: { 632 | url: '=' + requestURL('message', 'sendMedia'), 633 | method: 'POST', 634 | }, 635 | }, 636 | }, 637 | ]; 638 | 639 | export const mediaBase64MessgeProperties: INodeProperties[] = [ 640 | { 641 | displayName: 'File Name', 642 | name: 'fileNameProperty', 643 | required: true, 644 | placeholder: 'table.xlsx', 645 | hint: 'Para que o tipo de arquivo seja identificado pelo WhatsApp, a extensão deve ser informada pelo WhatsApp.', 646 | type: 'string', 647 | default: '', 648 | routing: { 649 | send: { type: 'body', property: 'mediaMessage.fileName' }, 650 | }, 651 | displayOptions: { 652 | show: { 653 | resource: ['sendMessage'], 654 | operation: ['sendMediaBase64'], 655 | }, 656 | }, 657 | }, 658 | 659 | { 660 | displayName: 'Media Type', 661 | name: 'mediaTypeProperty', 662 | required: true, 663 | placeholder: '', 664 | type: 'options', 665 | options: [ 666 | { name: 'Image', value: 'image' }, 667 | { name: 'Document', value: 'document' }, 668 | { name: 'Video', value: 'video' }, 669 | { name: 'Sticker', value: 'sticker' }, 670 | ], 671 | default: 'image', 672 | routing: { 673 | send: { type: 'body', property: 'mediaMessage.mediaType' }, 674 | }, 675 | displayOptions: { 676 | show: { 677 | resource: ['sendMessage'], 678 | operation: ['sendMediaBase64'], 679 | }, 680 | }, 681 | }, 682 | 683 | { 684 | displayName: 'Base64', 685 | name: 'base64Property', 686 | required: true, 687 | type: 'string', 688 | default: '', 689 | routing: { 690 | send: { type: 'body', property: 'mediaMessage.base64' }, 691 | }, 692 | displayOptions: { 693 | show: { 694 | resource: ['sendMessage'], 695 | operation: ['sendMediaBase64'], 696 | }, 697 | }, 698 | }, 699 | 700 | { 701 | displayName: 'Media Caption', 702 | name: 'mediaCaptionProperty', 703 | type: 'fixedCollection', 704 | typeOptions: { multipleValues: false }, 705 | default: {}, 706 | options: [ 707 | { 708 | displayName: 'Caption', 709 | name: 'captionProperty', 710 | values: [ 711 | { 712 | displayName: 'Caption', 713 | name: 'caption', 714 | type: 'string', 715 | default: '', 716 | placeholder: 'caption - description - title', 717 | routing: { 718 | send: { type: 'body', property: 'mediaMessage.caption' }, 719 | }, 720 | }, 721 | ], 722 | }, 723 | ], 724 | displayOptions: { 725 | show: { 726 | resource: ['sendMessage'], 727 | operation: ['sendMediaBase64'], 728 | }, 729 | }, 730 | }, 731 | 732 | { 733 | displayName: 'Set Routing', 734 | name: 'setRouting', 735 | type: 'hidden', 736 | default: '', 737 | displayOptions: { 738 | show: { 739 | resource: ['sendMessage'], 740 | operation: ['sendMediaBase64'], 741 | }, 742 | }, 743 | routing: { 744 | request: { 745 | url: '=' + requestURL('message', 'sendMediaBase64'), 746 | method: 'POST', 747 | }, 748 | }, 749 | }, 750 | ]; 751 | 752 | export const whatsAppAudioProperties: INodeProperties[] = [ 753 | { 754 | displayName: 'WhatsApp Audio', 755 | name: 'whatsAppAudioProperty', 756 | required: true, 757 | placeholder: 'url or base64', 758 | default: '', 759 | type: 'string', 760 | routing: { 761 | send: { type: 'body', property: 'whatsappAudio.audio' }, 762 | request: { 763 | url: '=' + requestURL('message', 'sendWhatsAppAudio'), 764 | method: 'POST', 765 | }, 766 | }, 767 | displayOptions: { 768 | show: { 769 | resource: ['sendMessage'], 770 | operation: ['sendWhatsAppAudio'], 771 | }, 772 | }, 773 | }, 774 | ]; 775 | 776 | export const locationProperties: INodeProperties[] = [ 777 | { 778 | displayName: 'Latitude', 779 | name: 'latitudeProperty', 780 | required: true, 781 | default: '', 782 | type: 'number', 783 | placeholder: '-20.32568196333534', 784 | routing: { 785 | send: { type: 'body', property: 'locationMessage.latitude' }, 786 | }, 787 | displayOptions: { 788 | show: { 789 | resource: ['sendMessage'], 790 | operation: ['sendLocation'], 791 | }, 792 | }, 793 | }, 794 | 795 | { 796 | displayName: 'Longitude', 797 | name: 'longitudeProperty', 798 | required: true, 799 | default: '', 800 | type: 'number', 801 | placeholder: '-20.32568196333534', 802 | routing: { 803 | send: { type: 'body', property: 'locationMessage.longitude' }, 804 | }, 805 | displayOptions: { 806 | show: { 807 | resource: ['sendMessage'], 808 | operation: ['sendLocation'], 809 | }, 810 | }, 811 | }, 812 | 813 | { 814 | displayName: 'Name', 815 | name: 'nameProperty', 816 | default: '', 817 | description: 'City name - state of ... - district.', 818 | type: 'string', 819 | routing: { 820 | send: { type: 'body', property: 'locationMessage.name' }, 821 | }, 822 | displayOptions: { 823 | show: { 824 | resource: ['sendMessage'], 825 | operation: ['sendLocation'], 826 | }, 827 | }, 828 | }, 829 | 830 | { 831 | displayName: 'Address', 832 | name: 'addressProperty', 833 | default: '', 834 | description: 'Location address - landmark - location name', 835 | type: 'string', 836 | routing: { 837 | send: { type: 'body', property: 'locationMessage.name' }, 838 | }, 839 | displayOptions: { 840 | show: { 841 | resource: ['sendMessage'], 842 | operation: ['sendLocation'], 843 | }, 844 | }, 845 | }, 846 | 847 | { 848 | displayName: 'Add Type Buttons', 849 | name: 'addTypeButtonsProperty', 850 | description: 'Optional', 851 | type: 'fixedCollection', 852 | typeOptions: { multipleValues: false }, 853 | options: [ 854 | { 855 | displayName: 'Reply Buttons', 856 | name: 'replyButtonsProperty', 857 | values: [ 858 | { 859 | displayName: 'Buttons for Location', 860 | name: 'buttonsLocation', 861 | required: true, 862 | placeholder: `[Array:[{displayText: 'Button Text', buttonId: 'btnId01'}]]`, 863 | type: 'json', 864 | default: [], 865 | description: 'Map a JSON directly to this field', 866 | hint: 'Maximum of three buttons ', 867 | routing: { 868 | send: { type: 'body', property: 'locationMessage.commonButtons.buttons' }, 869 | }, 870 | }, 871 | ], 872 | }, 873 | ], 874 | default: {}, 875 | displayOptions: { 876 | show: { 877 | resource: ['sendMessage'], 878 | operation: ['sendLocation'], 879 | }, 880 | }, 881 | }, 882 | 883 | { 884 | displayName: 'Set Routing', 885 | name: 'setRouting', 886 | type: 'hidden', 887 | default: '', 888 | displayOptions: { 889 | show: { 890 | resource: ['sendMessage'], 891 | operation: ['sendLocation'], 892 | }, 893 | }, 894 | routing: { 895 | request: { 896 | url: '=' + requestURL('message', 'sendLocation'), 897 | method: 'POST', 898 | }, 899 | }, 900 | }, 901 | ]; 902 | 903 | export const listProperties: INodeProperties[] = [ 904 | { 905 | displayName: 'List Title', 906 | name: 'listTitleProperty', 907 | required: true, 908 | default: '', 909 | type: 'string', 910 | routing: { 911 | send: { type: 'body', property: 'listMessage.title' }, 912 | }, 913 | displayOptions: { 914 | show: { 915 | resource: ['sendMessage'], 916 | operation: ['sendList'], 917 | }, 918 | }, 919 | }, 920 | 921 | { 922 | displayName: 'Description', 923 | name: 'listDescriptionProperty', 924 | required: true, 925 | default: '', 926 | type: 'string', 927 | routing: { 928 | send: { type: 'body', property: 'listMessage.description' }, 929 | }, 930 | displayOptions: { 931 | show: { 932 | resource: ['sendMessage'], 933 | operation: ['sendList'], 934 | }, 935 | }, 936 | }, 937 | 938 | { 939 | displayName: 'Button Text', 940 | name: 'buttonTextProperty', 941 | required: true, 942 | default: '', 943 | description: 'List clickable button title', 944 | type: 'string', 945 | routing: { 946 | send: { type: 'body', property: 'listMessage.buttonText' }, 947 | }, 948 | displayOptions: { 949 | show: { 950 | resource: ['sendMessage'], 951 | operation: ['sendList'], 952 | }, 953 | }, 954 | }, 955 | 956 | { 957 | displayName: 'Footer Text', 958 | name: 'footerTextProperty', 959 | default: '', 960 | description: 'Optional', 961 | type: 'string', 962 | routing: { 963 | send: { type: 'body', property: 'listMessage.footerText' }, 964 | }, 965 | displayOptions: { 966 | show: { 967 | resource: ['sendMessage'], 968 | operation: ['sendList'], 969 | }, 970 | }, 971 | }, 972 | 973 | { 974 | displayName: 'List Field Type', 975 | name: 'listFieldTypeProperty', 976 | required: true, 977 | noDataExpression: true, 978 | placeholder: '', 979 | type: 'options', 980 | options: [ 981 | { name: 'Collection', value: 'collection' }, 982 | { name: 'JSON', value: 'json' }, 983 | ], 984 | default: 'collection', 985 | displayOptions: { 986 | show: { 987 | resource: ['sendMessage'], 988 | operation: ['sendList'], 989 | }, 990 | }, 991 | }, 992 | 993 | { 994 | displayName: 'List Section Fields', 995 | name: 'listSectionFieldsProperty', 996 | placeholder: 'Add Section', 997 | required: true, 998 | default: {}, 999 | type: 'fixedCollection', 1000 | typeOptions: { multipleValues: true }, 1001 | options: [ 1002 | { 1003 | displayName: 'Sections', 1004 | name: 'sections', 1005 | values: [ 1006 | { 1007 | displayName: 'Section Title', 1008 | name: 'title', 1009 | required: true, 1010 | type: 'string', 1011 | default: '', 1012 | }, 1013 | { 1014 | displayName: 'Rows', 1015 | name: 'rowsProperty', 1016 | placeholder: 'Add Row', 1017 | required: true, 1018 | default: {}, 1019 | type: 'fixedCollection', 1020 | typeOptions: { multipleValues: true }, 1021 | options: [ 1022 | { 1023 | displayName: 'Row', 1024 | name: 'rows', 1025 | values: [ 1026 | { 1027 | displayName: 'Row Title', 1028 | name: 'title', 1029 | required: true, 1030 | type: 'string', 1031 | default: '', 1032 | }, 1033 | { 1034 | displayName: 'Description', 1035 | name: 'description', 1036 | required: true, 1037 | type: 'string', 1038 | default: '', 1039 | }, 1040 | { 1041 | displayName: 'Row ID', 1042 | name: 'rowId', 1043 | required: true, 1044 | type: 'string', 1045 | default: '', 1046 | description: 'Os rowIds devem ser únicos', 1047 | }, 1048 | ], 1049 | }, 1050 | ], 1051 | }, 1052 | ], 1053 | }, 1054 | ], 1055 | routing: { 1056 | send: { type: 'body', property: 'listMessage' }, 1057 | }, 1058 | displayOptions: { 1059 | show: { 1060 | resource: ['sendMessage'], 1061 | operation: ['sendList'], 1062 | listFieldTypeProperty: ['collection'], 1063 | }, 1064 | }, 1065 | }, 1066 | 1067 | { 1068 | displayName: 'List Section JSON', 1069 | name: 'listSectionJSONProperty', 1070 | required: true, 1071 | description: 'Single elements Array', 1072 | placeholder: `[Array:[title:'Section Title',rows:[{title:'Row Title',description:'Description',rowId:'rowId01'}]]]`, 1073 | type: 'json', 1074 | default: [], 1075 | routing: { 1076 | send: { type: 'body', property: 'listMessage.sections' }, 1077 | }, 1078 | displayOptions: { 1079 | show: { 1080 | resource: ['sendMessage'], 1081 | operation: ['sendList'], 1082 | listFieldTypeProperty: ['json'], 1083 | }, 1084 | }, 1085 | }, 1086 | 1087 | { 1088 | displayName: 'Set Routing', 1089 | name: 'setRouting', 1090 | type: 'hidden', 1091 | default: '', 1092 | displayOptions: { 1093 | show: { 1094 | resource: ['sendMessage'], 1095 | operation: ['sendList'], 1096 | }, 1097 | }, 1098 | routing: { 1099 | request: { 1100 | url: '=' + requestURL('message', 'sendList'), 1101 | method: 'POST', 1102 | }, 1103 | send: { preSend: [sendListMessage] }, 1104 | }, 1105 | }, 1106 | ]; 1107 | 1108 | export const linkPreviewProperties: INodeProperties[] = [ 1109 | { 1110 | displayName: 'Everyone', 1111 | name: 'modeLinkPreviewProperty', 1112 | type: 'options', 1113 | options: [ 1114 | { name: 'Common', value: 'common' }, 1115 | { name: 'Template', value: 'template' }, 1116 | ], 1117 | default: 'common', 1118 | routing: { 1119 | send: { type: 'query', property: 'mode' }, 1120 | }, 1121 | displayOptions: { 1122 | show: { 1123 | resource: ['sendMessage'], 1124 | operation: ['sendLinkPreview'], 1125 | }, 1126 | }, 1127 | }, 1128 | 1129 | { 1130 | displayName: 'Url', 1131 | name: 'urlLinkPreviewProperty', 1132 | required: true, 1133 | placeholder: 'https://github.com/jrCleber', 1134 | type: 'string', 1135 | default: '', 1136 | routing: { 1137 | send: { type: 'body', property: 'linkPreview.url' }, 1138 | }, 1139 | displayOptions: { 1140 | show: { 1141 | resource: ['sendMessage'], 1142 | operation: ['sendLinkPreview'], 1143 | }, 1144 | }, 1145 | }, 1146 | 1147 | { 1148 | displayName: 'Text', 1149 | name: 'textLinkPreviewProperty', 1150 | description: 'Required for "template" mode', 1151 | type: 'string', 1152 | default: '', 1153 | routing: { 1154 | send: { type: 'body', property: 'linkPreview.text' }, 1155 | }, 1156 | displayOptions: { 1157 | show: { 1158 | resource: ['sendMessage'], 1159 | operation: ['sendLinkPreview'], 1160 | }, 1161 | }, 1162 | }, 1163 | 1164 | { 1165 | displayName: 'Options', 1166 | name: 'optionsLinkPreviewProperty', 1167 | type: 'fixedCollection', 1168 | typeOptions: { multipleValues:false }, 1169 | default: {}, 1170 | options: [ 1171 | { 1172 | displayName: 'Link Preview Options', 1173 | name: 'optionsProperty', 1174 | values: [ 1175 | { 1176 | displayName: 'Title', 1177 | name: 'titleLinkPreviewProperty', 1178 | type: 'string', 1179 | default: '', 1180 | routing: { 1181 | send: { type:'body', property: 'linkPreview.title' }, 1182 | }, 1183 | }, 1184 | { 1185 | displayName: 'Description', 1186 | name: 'descriptionLinkPreviewProperty', 1187 | type: 'string', 1188 | default: '', 1189 | routing: { 1190 | send: { type:'body', property: 'linkPreview.description' }, 1191 | }, 1192 | }, 1193 | { 1194 | displayName: 'Image', 1195 | name: 'imageLinkPreviewProperty', 1196 | type: 'string', 1197 | default: '', 1198 | description: 'URL or base64', 1199 | routing: { 1200 | send: { type:'body', property: 'linkPreview.image' }, 1201 | }, 1202 | }, 1203 | ], 1204 | }, 1205 | ], 1206 | displayOptions: { 1207 | show: { 1208 | resource: ['sendMessage'], 1209 | operation: ['sendLinkPreview'], 1210 | }, 1211 | }, 1212 | }, 1213 | 1214 | { 1215 | displayName: 'Set Routing', 1216 | name: 'setRouting', 1217 | type: 'hidden', 1218 | default: '', 1219 | displayOptions: { 1220 | show: { 1221 | resource: ['sendMessage'], 1222 | operation: ['sendLinkPreview'], 1223 | }, 1224 | }, 1225 | routing: { 1226 | request: { 1227 | url: '=' + requestURL('message', 'sendLinkPreview'), 1228 | method: 'POST', 1229 | }, 1230 | }, 1231 | }, 1232 | ]; 1233 | 1234 | export const contactProperties: INodeProperties[] = [ 1235 | { 1236 | displayName: 'Contact Field Type', 1237 | name: 'contactTypeProperty', 1238 | required: true, 1239 | noDataExpression: true, 1240 | placeholder: '', 1241 | type: 'options', 1242 | options: [ 1243 | { name: 'Collection', value: 'collection' }, 1244 | { name: 'JSON', value: 'json' }, 1245 | ], 1246 | default: 'collection', 1247 | displayOptions: { 1248 | show: { 1249 | resource: ['sendMessage'], 1250 | operation: ['sendContact'], 1251 | }, 1252 | }, 1253 | }, 1254 | 1255 | { 1256 | displayName: 'Contact Fields', 1257 | name: 'contactFieldsProperty', 1258 | required: true, 1259 | default: {}, 1260 | placeholder: '', 1261 | type: 'fixedCollection', 1262 | typeOptions: { multipleValues: true }, 1263 | options: [ 1264 | { 1265 | displayName: 'Contacts', 1266 | name: 'contacts', 1267 | values: [ 1268 | { 1269 | displayName: 'Contact Name', 1270 | name: 'fullName', 1271 | placeholder: 'name', 1272 | required: true, 1273 | default: '', 1274 | type: 'string', 1275 | }, 1276 | { 1277 | displayName: 'Whatsapp Unuque ID', 1278 | name: 'wuid', 1279 | placeholder: '5531900000000', 1280 | required: true, 1281 | default: '', 1282 | type: 'string', 1283 | }, 1284 | { 1285 | displayName: 'Phone Number', 1286 | name: 'phoneNumber', 1287 | placeholder: '+55 31 9 0000-0000', 1288 | required: true, 1289 | default: '', 1290 | type: 'string', 1291 | }, 1292 | ], 1293 | }, 1294 | ], 1295 | routing: { 1296 | send: { type: 'body', property: 'contactMessage' }, 1297 | }, 1298 | displayOptions: { 1299 | show: { 1300 | resource: ['sendMessage'], 1301 | operation: ['sendContact'], 1302 | contactTypeProperty: ['collection'], 1303 | }, 1304 | }, 1305 | }, 1306 | 1307 | { 1308 | displayName: 'Contact JSON', 1309 | name: 'contactJSONProperty', 1310 | required: true, 1311 | description: 'Single elements Array', 1312 | placeholder: `[Array:[fullName:'Contact name',wuid:'5531900000000',phoneNumber]]`, 1313 | type: 'json', 1314 | default: [], 1315 | routing: { 1316 | send: { type: 'body', property: 'contactMessage' }, 1317 | }, 1318 | displayOptions: { 1319 | show: { 1320 | resource: ['sendMessage'], 1321 | operation: ['sendContact'], 1322 | contactTypeProperty: ['json'], 1323 | }, 1324 | }, 1325 | }, 1326 | 1327 | { 1328 | displayName: 'Set Routing', 1329 | name: 'setRouting', 1330 | type: 'hidden', 1331 | default: '', 1332 | displayOptions: { 1333 | show: { 1334 | resource: ['sendMessage'], 1335 | operation: ['sendContact'], 1336 | }, 1337 | }, 1338 | routing: { 1339 | request: { 1340 | url: '=' + requestURL('message', 'sendContact'), 1341 | method: 'POST', 1342 | }, 1343 | send: { preSend: [sendContactMessage] }, 1344 | }, 1345 | }, 1346 | ]; 1347 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "n8n-nodes-codechat", 3 | "version": "0.2.0", 4 | "description": "Rest api for communication with WhatsApp", 5 | "keywords": [ 6 | "n8n", 7 | "node", 8 | "no-code", 9 | "n8n-nodes-codechat", 10 | "n8n-community-nodes-codechat", 11 | "n8n-community-node-codechat", 12 | "n8n-community-node-package", 13 | "chat", 14 | "communication", 15 | "message", 16 | "send message", 17 | "whatsapp", 18 | "js-whatsapp", 19 | "whatsapp-api", 20 | "whatsapp-web", 21 | "whatsapp", 22 | "whatsapp-chat", 23 | "whatsapp-group", 24 | "automation", 25 | "multi-device", 26 | "bot" 27 | ], 28 | "license": "MIT", 29 | "homepage": "https://github.com/code-chat-br", 30 | "author": { 31 | "name": "codechat", 32 | "email": "suporte@codechat.rest" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "https://github.com/code-chat-br/n8n-node-codechat" 37 | }, 38 | "main": "index.js", 39 | "scripts": { 40 | "build": "rm -rf ./dist && tsc && gulp build:icons", 41 | "dev": "tsc --watch", 42 | "format": "prettier nodes credentials --write", 43 | "lint": "tslint -p tsconfig.json -c tslint.json && eslint nodes credentials package.json", 44 | "lintfix": "tslint --fix -p tsconfig.json -c tslint.json && eslint nodes credentials package.json --fix", 45 | "prepublishOnly": "npm run build && npm run lint -c .eslintrc.prepublish.js nodes credentials package.json" 46 | }, 47 | "files": [ 48 | "dist" 49 | ], 50 | "n8n": { 51 | "n8nNodesApiVersion": 1, 52 | "credentials": [ 53 | "dist/credentials/CodeChatCredentialsApi.credentials.js" 54 | ], 55 | "nodes": [ 56 | "dist/nodes/CodeChat/CodeChat.node.js" 57 | ] 58 | }, 59 | "devDependencies": { 60 | "@types/express": "^4.17.6", 61 | "@types/request-promise-native": "~1.0.15", 62 | "@typescript-eslint/eslint-plugin": "^5.39.0", 63 | "@typescript-eslint/parser": "^5.29.0", 64 | "eslint": "^8.25.0", 65 | "eslint-config-prettier": "^8.5.0", 66 | "eslint-plugin-n8n-nodes-base": "^1.5.4", 67 | "eslint-plugin-prettier": "^4.2.1", 68 | "gulp": "^4.0.2", 69 | "n8n-core": "^0.138.0", 70 | "n8n-workflow": "^0.107.0", 71 | "prettier": "^2.7.1", 72 | "tslint": "^6.1.2", 73 | "typescript": "~4.6.0" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "es2017", 5 | "es2019" 6 | ], 7 | "types": [ 8 | "node", 9 | ], 10 | "module": "commonjs", 11 | "noImplicitAny": true, 12 | "removeComments": true, 13 | "strictNullChecks": true, 14 | "strict": true, 15 | "preserveConstEnums": true, 16 | "resolveJsonModule": true, 17 | "declaration": true, 18 | "outDir": "./dist/", 19 | "target": "es2019", 20 | "sourceMap": true, 21 | "esModuleInterop": true, 22 | "useUnknownInCatchVariables": false, 23 | }, 24 | "include": [ 25 | "credentials/**/*", 26 | "nodes/**/*", 27 | "nodes/**/*.json", 28 | "package.json", 29 | ], 30 | } 31 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "linterOptions": { 3 | "exclude": [ 4 | "node_modules/**/*" 5 | ] 6 | }, 7 | "defaultSeverity": "error", 8 | "jsRules": {}, 9 | "rules": { 10 | "array-type": [ 11 | true, 12 | "array-simple" 13 | ], 14 | "arrow-return-shorthand": true, 15 | "ban": [ 16 | true, 17 | { 18 | "name": "Array", 19 | "message": "tsstyle#array-constructor" 20 | } 21 | ], 22 | "ban-types": [ 23 | true, 24 | [ 25 | "Object", 26 | "Use {} instead." 27 | ], 28 | [ 29 | "String", 30 | "Use 'string' instead." 31 | ], 32 | [ 33 | "Number", 34 | "Use 'number' instead." 35 | ], 36 | [ 37 | "Boolean", 38 | "Use 'boolean' instead." 39 | ] 40 | ], 41 | "class-name": true, 42 | "curly": [ 43 | true, 44 | "ignore-same-line" 45 | ], 46 | "forin": true, 47 | "jsdoc-format": true, 48 | "label-position": true, 49 | "indent": [ 50 | true, 51 | "tabs", 52 | 2 53 | ], 54 | "member-access": [ 55 | true, 56 | "no-public" 57 | ], 58 | "new-parens": true, 59 | "no-angle-bracket-type-assertion": true, 60 | "no-any": true, 61 | "no-arg": true, 62 | "no-conditional-assignment": true, 63 | "no-construct": true, 64 | "no-debugger": true, 65 | "no-default-export": true, 66 | "no-duplicate-variable": true, 67 | "no-inferrable-types": true, 68 | "ordered-imports": [ 69 | true, 70 | { 71 | "import-sources-order": "any", 72 | "named-imports-order": "case-insensitive" 73 | } 74 | ], 75 | "no-namespace": [ 76 | true, 77 | "allow-declarations" 78 | ], 79 | "no-reference": true, 80 | "no-string-throw": true, 81 | "no-unused-expression": true, 82 | "no-var-keyword": true, 83 | "object-literal-shorthand": true, 84 | "only-arrow-functions": [ 85 | true, 86 | "allow-declarations", 87 | "allow-named-functions" 88 | ], 89 | "prefer-const": true, 90 | "radix": true, 91 | "semicolon": [ 92 | true, 93 | "always", 94 | "ignore-bound-class-methods" 95 | ], 96 | "switch-default": true, 97 | "trailing-comma": [ 98 | true, 99 | { 100 | "multiline": { 101 | "objects": "always", 102 | "arrays": "always", 103 | "functions": "always", 104 | "typeLiterals": "ignore" 105 | }, 106 | "esSpecCompliant": true 107 | } 108 | ], 109 | "triple-equals": [ 110 | true, 111 | "allow-null-check" 112 | ], 113 | "use-isnan": true, 114 | "quotes": [ 115 | "error", 116 | "single" 117 | ], 118 | "variable-name": [ 119 | true, 120 | "check-format", 121 | "ban-keywords", 122 | "allow-leading-underscore", 123 | "allow-trailing-underscore" 124 | ] 125 | }, 126 | "rulesDirectory": [] 127 | } 128 | --------------------------------------------------------------------------------