├── .env
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── develop-pr.yml
│ ├── develop.yml
│ ├── main-pr.yml
│ ├── main.yml
│ └── publish.yml
├── .gitignore
├── .husky
├── .gitignore
├── pre-commit
└── prepare-commit-msg
├── .prettierrc
├── CHANGELOG.md
├── LICENSE
├── README.md
├── jest.config.json
├── package-lock.json
├── package.json
├── src
├── index.ts
├── modules
│ ├── chat.ts
│ ├── instances.ts
│ └── sarufi.ts
└── shared
│ ├── helpers
│ ├── error.helper.ts
│ └── id.helper.ts
│ └── interfaces
│ ├── auth.interface.ts
│ ├── bot.interface.ts
│ ├── conversation.interface.ts
│ └── shared.interface.ts
├── test
├── index.d.ts
├── mocks
│ └── mocks.constants.ts
├── modules
│ └── index.spec.ts
└── shared
│ ├── error.helper.spec.ts
│ └── id.helper.spec.ts
└── tsconfig.json
/.env:
--------------------------------------------------------------------------------
1 | # Local env vars for debugging
2 | TS_NODE_IGNORE="false"
3 | TS_NODE_FILES="true"
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | src/types/global.d.ts
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "plugins": ["@typescript-eslint", "node", "prettier"],
5 | "parserOptions": {
6 | "sourceType": "module",
7 | "tsconfigRootDir": "./",
8 | "project": ["./tsconfig.json"]
9 | },
10 | "extends": [
11 | "eslint:recommended",
12 | "plugin:node/recommended",
13 | "plugin:@typescript-eslint/eslint-recommended",
14 | "plugin:@typescript-eslint/recommended",
15 | "plugin:@typescript-eslint/recommended-requiring-type-checking",
16 | "plugin:prettier/recommended"
17 | ],
18 | "rules": {
19 | "prettier/prettier": "warn",
20 | "node/no-missing-import": "off",
21 | "node/no-empty-function": "off",
22 | "node/no-unsupported-features/es-syntax": "off",
23 | "node/no-missing-require": "off",
24 | "node/shebang": "off",
25 | "@typescript-eslint/no-use-before-define": "off",
26 | "quotes": ["warn", "single", { "avoidEscape": true }],
27 | "node/no-unpublished-import": "off",
28 | "@typescript-eslint/no-unsafe-assignment": "off",
29 | "@typescript-eslint/no-var-requires": "off",
30 | "@typescript-eslint/ban-ts-comment": "off",
31 | "@typescript-eslint/no-explicit-any": "off"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set the repository to show as TypeScript rather than JS in GitHub
2 | *.js linguist-detectable=false
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "🐛 Bug Report"
3 | about: Report a reproducible bug or regression.
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Current Behavior
11 |
12 |
13 |
14 | ## Expected Behavior
15 |
16 |
17 |
18 | ## Steps to Reproduce the Problem
19 |
20 | 1.
21 | 1.
22 | 1.
23 |
24 | ## Environment
25 |
26 | - Version:
27 | - Platform:
28 | - Node.js Version:
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: ✨ Feature request
3 | about: Suggest an amazing new idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 | ---
8 |
9 | ## Feature Request
10 |
11 | **Is your feature request related to a problem? Please describe.**
12 |
13 |
14 |
15 | **Describe the solution you'd like**
16 |
17 |
18 |
19 | **Describe alternatives you've considered**
20 |
21 |
22 |
23 | ## Are you willing to resolve this issue by submitting a Pull Request?
24 |
25 |
28 |
29 | - [ ] Yes, I have the time, and I know how to start.
30 | - [ ] Yes, I have the time, but I don't know how to start. I would need guidance.
31 | - [ ] No, I don't have the time, although I believe I could do it if I had the time...
32 | - [ ] No, I don't have the time and I wouldn't even know how to start.
33 |
34 |
37 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
7 |
8 | ### Description of change
9 |
10 |
23 |
24 | ### Pull-Request Checklist
25 |
26 |
31 |
32 | - [ ] Code is up-to-date with the `main` branch
33 | - [ ] `npm run lint` passes with this change
34 | - [ ] `npm run test` passes with this change
35 | - [ ] This pull request links relevant issues as `Fixes #0000`
36 | - [ ] There are new or updated unit tests validating the change
37 | - [ ] Documentation has been updated to reflect this change
38 | - [ ] The new commits follow conventions outlined in the [conventional commit spec](https://www.conventionalcommits.org/en/v1.0.0/)
39 |
40 |
43 |
--------------------------------------------------------------------------------
/.github/workflows/develop-pr.yml:
--------------------------------------------------------------------------------
1 | name: CREATE DEVELOP PR
2 |
3 | on:
4 | push:
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 |
10 | strategy:
11 | matrix:
12 | node-version: [15.x]
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: 🔀
16 | uses: BaharaJr/create-pr@0.0.1
17 | with:
18 | GITHUB_TOKEN: ${{secrets.TOKEN}}
19 | DESTINATION_BRANCH: develop
20 | KEYWORD: release
21 |
--------------------------------------------------------------------------------
/.github/workflows/develop.yml:
--------------------------------------------------------------------------------
1 | name: RELEASE
2 | on:
3 | pull_request:
4 | branches: [develop]
5 |
6 | jobs:
7 | release:
8 | name: Release
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: 🚚
13 | uses: actions/checkout@v2
14 | with:
15 | fetch-depth: 0
16 |
17 | - name: 🧱
18 | uses: actions/setup-node@v2
19 | with:
20 | node-version: lts/*
21 |
22 | - name: ➕
23 | run: npm ci
24 |
25 | - name: 💚
26 | run: npm run package
27 |
--------------------------------------------------------------------------------
/.github/workflows/main-pr.yml:
--------------------------------------------------------------------------------
1 | name: CREATE RELEASE PR
2 |
3 | on:
4 | push:
5 | branches: [develop]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 |
11 | strategy:
12 | matrix:
13 | node-version: [15.x]
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: 🔀
17 | uses: BaharaJr/create-pr@0.0.1
18 | with:
19 | GITHUB_TOKEN: ${{secrets.TOKEN}}
20 | DESTINATION_BRANCH: main
21 | KEYWORD: build
22 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: RELEASE
2 | on:
3 | pull_request:
4 | branches: [main]
5 |
6 | jobs:
7 | release:
8 | name: Release
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: 🚚
13 | uses: actions/checkout@v2
14 | with:
15 | fetch-depth: 0
16 |
17 | - name: 🧱
18 | uses: actions/setup-node@v2
19 | with:
20 | node-version: lts/*
21 |
22 | - name: ➕
23 | run: npm i
24 |
25 | - name: 💚
26 | run: npm run package
27 |
28 | - name: 🚀
29 | env:
30 | GITHUB_TOKEN: ${{ secrets.TOKEN }}
31 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
32 | run: npx semantic-release
33 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish to NPM
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'main'
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: 🚚
13 | uses: actions/checkout@v2
14 | - name: set global attributes
15 | run: |
16 | git config --global user.email "neicoreadams@gmail.com"
17 | git config --global user.name "neicore"
18 | git remote set-url origin https://x-access-token:${{ secrets.TOKEN }}@github.com/${{ github.repository }}
19 |
20 | - name: Setup Node
21 | uses: actions/setup-node@v2
22 | with:
23 | node-version: '14.x'
24 | registry-url: 'https://registry.npmjs.org'
25 | - name: Install dependencies and build 🔧
26 | run: npm install && npm run build
27 | - name: Publish package on NPM 📦
28 | run: npm publish --access public
29 | env:
30 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
31 | # update version
32 | - name: install jq
33 | run: |
34 | sudo apt-get update
35 | sudo apt-get -y install jq
36 | - uses: actions/checkout@v2
37 | with:
38 | ref: develop
39 | token: ${{ secrets.TOKEN }}
40 | fetch-depth: 0
41 | - name: update develop branch
42 | run: |
43 | git checkout develop
44 | git config pull.rebase false
45 | git pull origin develop --allow-unrelated-histories
46 |
47 | - name: get version
48 | run: |
49 | PACKAGE_VERSION=$(cat ./package.json | jq '.version' | tr -d '"')
50 | echo "::set-output name=PACKAGE_VERSION::$(cat ./package.json | jq '.version' | tr -d '"')"
51 | id: version
52 | - name: bump-version
53 | id: bump_version
54 | uses: flexcodelabs/bump-version@0.0.2
55 | with:
56 | GITHUB_TOKEN: ${{secrets.TOKEN}}
57 | PACKAGE_VERSION: ${{ steps.version.outputs.PACKAGE_VERSION }}
58 | DELETE_BRANCH: false
59 | CHANGELOG_PATH: ./CHANGELOG.md
60 | PACKAGE_JSON_PATH: ./package.json
61 | # Commit and push the latest version
62 | - name: update develop branch
63 | run: |
64 | git checkout develop
65 | git config pull.rebase false
66 | git add ./package.json
67 | git add ./CHANGELOG.md
68 | git commit -m "Skip CI: update package version and changelog"
69 | git pull origin develop --allow-unrelated-histories
70 | git push origin develop
71 |
72 |
73 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # Snowpack dependency directory (https://snowpack.dev/)
45 | web_modules/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env.test
73 | .env.local
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 | .parcel-cache
78 |
79 | # Next.js build output
80 | .next
81 | out
82 |
83 | # Nuxt.js build / generate output
84 | .nuxt
85 | dist
86 |
87 | # Gatsby files
88 | .cache/
89 | # Comment in the public line in if your project uses Gatsby and not Next.js
90 | # https://nextjs.org/blog/next-9-1#public-directory-support
91 | # public
92 |
93 | # vuepress build output
94 | .vuepress/dist
95 |
96 | # Serverless directories
97 | .serverless/
98 |
99 | # FuseBox cache
100 | .fusebox/
101 |
102 | # DynamoDB Local files
103 | .dynamodb/
104 |
105 | # TernJS port file
106 | .tern-port
107 |
108 | # Stores VSCode versions used for testing VSCode extensions
109 | .vscode-test
110 |
111 | # yarn v2
112 | .yarn/cache
113 | .yarn/unplugged
114 | .yarn/build-state.yml
115 | .yarn/install-state.gz
116 | .pnp.*
117 |
118 | # Compiled code
119 | lib/
120 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/.husky/prepare-commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | exec suply supply typo in README. @[`Alfeo`](https://github.com/Pheogrammer)
72 | - Improve README JSON code blocks. @[`Alpha`](https://github.com/alphaolomi)
73 | - Fix wether whether typo in README. @[`Shanas`](https://github.com/mrshanas)
74 |
75 | ## [0.0.3-Beta] - 2022-09-30
76 |
77 | ### Added
78 |
79 | - Added axios xhr adaptor custom typings for mocking tests.
80 | - Add optional token to every request for persisted tokens.
81 |
82 | ### Changed
83 |
84 | - Fixed error with failed typings for chat method on bot response.
85 | - Fix typo in README file and note for optional url.
86 | - Update README file for changed getBots request parameters and new added features.
87 |
88 | ## [0.0.2-Beta] - 2022-09-30
89 |
90 | ### Added
91 |
92 | - Added all Sarufi features.
93 | - Added tests for all methods and helpers.
94 |
95 | ## [0.0.5] - 2023-05-24
96 |
97 | ### Changed
98 |
99 | - Change auth URL
100 | - Bump package version
101 |
102 | ### Added
103 |
104 | - Build to CJS
105 | - Remove external dependencies
106 |
107 | ## [0.0.6] - 2023-06-28
108 |
109 | ### Changed
110 |
111 | - Change authentication approach to api_key
112 | - Update README file explaining new authentication approach (api_key)
113 | - Bump package version
114 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Flexcode Labs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NODEJS SARUFI SDK
2 |
3 | 
4 | 
5 | 
6 | 
7 |
8 | Sarufi NodeJS SDK to help you interact with SARUFI platform inspired by [Python Sarufi SDK](https://github.com/Neurotech-HQ/sarufi-python-sdk)
9 |
10 | ## Table of Contents
11 |
12 | 1. [Installation](#installation-and-use)
13 | 1.1 [Installation](#installation)
14 | 2. [Use](#use)
15 | 2.1. [Get your api key](#get-your-api-key)
16 | 2.1. [Create an empty chatbot](#create-an-empty-chatbot)
17 | 2.3. [Updating your bot](#updating-your-bot)
18 | 2.4. [Get bot by id](#get-bot-by-id)
19 | 2.5. [Get bots](#get-bots)
20 | 2.6. [Delete bot](#delete-bot)
21 | 2.7. [Start conversation](#start-conversation)
22 | 2.8. [All responses details](#all-responses-have)
23 |
24 | #
25 |
26 | ## Installation and Use
27 |
28 | ### Installation
29 |
30 | From terminal in the root directory of your project, run
31 |
32 | ```
33 | npm i sarufi
34 | ```
35 |
36 | ### Use
37 |
38 | ```js
39 | import sarufi from sarufi
40 | ```
41 |
42 | ### Get your api key
43 |
44 | Head to [Sarufi](https://www.sarufi.io/profile?tab=authorization), copy your api_key and login:
45 |
46 | ```js
47 | sarufi.login({ api_key: YOUR_API_KEY })
48 | ```
49 |
50 | ### Create an Empty chatbot
51 |
52 | We supply chatbot details
53 |
54 | > Request payload
55 |
56 | ```JSON
57 | {
58 | "name": "YOUR AWESOME BOT NAME",
59 | "description": "PUT DESCRIPTION HERE",
60 | "industry": "YOUR BOT INDUSTRY",
61 | "intents": {},
62 | "flows": {},
63 | }
64 | ```
65 |
66 | Then we call
67 |
68 | ```JS
69 | // call this first if you haven't logged in.
70 | sarufi.login({ api_key: YOUR_API_KEY })
71 |
72 | sarufi.createBot({bot: REQUEST PAYLOAD})
73 | ```
74 |
75 | `NB:` For detailed description of intents and flows to be used in conversation, refer to [Python Sarufi SDK Docs](https://docs.sarufi.io/docs/Getting%20started%20/create-a-simple-chatbot#help-me-order-a-pizza-intent)
76 |
77 | > Response for successful bot creation
78 |
79 | ```JSONC
80 | {
81 | "success": true,
82 | "bot": { "name": "YOUR AWESOME BOT NAME",
83 | "description": "PUT DESCRIPTION HERE",
84 | "user_id": "YOUR ID",
85 | "industry": "YOUR BOT INDUSTRY",
86 | "intents": {}, //intents if they were supplied
87 | "flows": {}, //flows if they were supplied
88 | "updated_at": "DATE THE BOT WAS LAST UPDATED",
89 | "id": "BOT ID",
90 | "model_name": "BOT MODEL NAME",
91 | "created_at": "DATE THE BOT WAS CREATED"
92 | },
93 | "chat": "({message: string, chat_id?: uknown}) => RETURNS CHAT RESPONSE" //A callable method that starts a chat with your bot, it takes in a string message and optional chat ID
94 | }
95 | ```
96 |
97 | ### Updating your bot
98 |
99 | Updating a bot, takes in the same arguments as creating a bot with addition of bot id
100 |
101 | > Request
102 |
103 | ```JS
104 |
105 | // api_key is optional
106 | sarufi.updateBot({bot: REQUEST PAYLOAD, id: YOUR BOT ID, api_key: YOUR_API_KEY})
107 |
108 | ```
109 |
110 | > Response on success, is the same as the response for creating a bot
111 |
112 | ### Get bot by id
113 |
114 | We call the following method on `sarufi` and pass the bot id
115 |
116 | > Request
117 |
118 | ```JS
119 | // api_key is optional
120 | sarufi.getBot({id: BOT ID, api_key: YOUR_API_KEY})
121 |
122 | ```
123 |
124 | > Response on success, is the same as the response for creating and updating a bot
125 |
126 | ### Get bots
127 |
128 | We call the following method on `sarufi` and pass the bot id
129 |
130 | > Request
131 |
132 | ```JS
133 | //For version 0.0.1-Beta
134 | sarufi.getBots()
135 |
136 | //For versions 0.0.2-Beta and above,
137 | sarufi.getBots({api_key: YOUR_API_KEY}) //This accepts optional paramemters url and api key for persisted authorization tokens.
138 |
139 | ```
140 |
141 | > Response on success
142 |
143 | ```JSONC
144 | {
145 | "success": true,
146 | "bots": [] // Array of all bots you created
147 | }
148 |
149 | ```
150 |
151 | ### Delete bot
152 |
153 | We call the following method on `sarufi` and pass the bot id
154 |
155 | ```JS
156 | // api_key is optional
157 | sarufi.deleteBot({id: BOT ID, api_key: YOUR_API_KEY})
158 |
159 | ```
160 |
161 | > Response on success
162 |
163 | ```JSON
164 | {
165 | "success": true,
166 | "message": "A MESSAGE STATING THAT YOUR BOT HAS BEEN DELETED"
167 | }
168 | ```
169 |
170 | ### Start conversation
171 |
172 | There are two methods for this, i.e
173 |
174 | 1. bot.chat() this requires us to get a bot we want to have a conversation with and calling a chat method
175 | 2. sarufi.chat() this requires a required message, required bot_id and an optional chat_id as request arguments
176 |
177 | ```JS
178 | // api_key is optional
179 |
180 | // bot.chat()
181 | const bot = await sarufi.getBot({id: 'OUR BOT ID', api_key: YOUR_API_KEY})
182 | await bot.chat({message: 'Yooh', channel: 'whatsapp'}) //channel is optional (Default is general)
183 |
184 | //sarufi.chat()
185 | await sarufi.chat({message: 'Hey', bot_id: bot.id, chat_id: 'HEYOO', api_key: YOUR_API_KEY, channel: 'whatsapp'})
186 |
187 | ```
188 |
189 | > Response on success
190 |
191 | ```JSONC
192 | // General Channel
193 | {
194 | "message": string| number | unknown | [string] | Record | [Record],
195 | "success": true,
196 | "memory": { [key: string]: string | unknown},
197 | [key: string]: string | unknown
198 | }
199 |
200 | // Whatsapp Channel
201 | {
202 | "actions": Array<{
203 | "send_message"?: string[];
204 | "send_button"?: any;
205 | "send_reply_button"?: {
206 | "type": string;
207 | "body": { "text": string };
208 | "action": any
209 | };
210 | "send_image"?: Array<{
211 | "link": string;
212 | "caption": string
213 | }>;
214 | "send_audio"?: any;
215 | "send_videos"?: any;
216 | }>;,
217 | "success": true,
218 | "memory": { [key: string]: string | unknown},
219 | }
220 | ```
221 |
222 | ### All Responses have
223 |
224 | 1. `Success` property that shows whether or not the request was successful
225 | 2. For failed requests, the response's success property will be false and additional properties for tracing will be added to the response object
226 |
227 | > Example of failed request
228 |
229 | ```JSONC
230 | {
231 | "success": false,
232 | "message": "MESSAGE", //an error message explaining the error
233 | "code": "string",
234 | "status": "NUMBER",
235 | }
236 | ```
237 |
238 | Although an error response can have more than those properties, when the status is 500, that will denote an error occured within the sarufi otherwise it will be from an error originating from the remote sarufi server.
239 |
240 | ### `All requests have an optional url property that can be passed in case the url changes in the future`
241 |
242 | `NB`: For detailed documentation, please visit the [Python Sarufi SDK Docs](https://docs.sarufi.io/)
243 |
244 | ### Developed and Maintained with ❤️ at [Flexcode Labs](https://flexcodelabs.com)
245 |
--------------------------------------------------------------------------------
/jest.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "preset": "ts-jest",
3 | "testEnvironment": "node",
4 | "testMatch": ["**/test/**/*.spec.ts"],
5 | "collectCoverageFrom": [
6 | "/src/**/*.ts",
7 | "!/src/types/**/*.ts"
8 | ],
9 | "globals": {
10 | "ts-jest": {
11 | "diagnostics": false,
12 | "isolatedModules": true
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sarufi",
3 | "version": "0.1.5",
4 | "description": "Sarufi NodeJs SDK to help you interact with Sarufi Conversational API",
5 | "main": "lib/index.js",
6 | "types": "lib/index.d.ts",
7 | "files": [
8 | "lib/**/*"
9 | ],
10 | "scripts": {
11 | "prebuild": "npm run clean",
12 | "build": "tsc && rm -rf ./lib/test && cp -r ./lib/src/* ./lib/ && rm -rf ./lib/src",
13 | "package": "npm run build -m && ncc build && cd ./lib && find . -name '*.js' -type f -delete && cd ../ && cp -r ./lib/* ./dist/ && rm -rf ./lib && mv dist lib",
14 | "clean": "rm -rf ./lib/",
15 | "cm": "cz",
16 | "release": "npm run package && npm publish",
17 | "lint": "eslint ./src/ --fix",
18 | "prepare": "husky install",
19 | "semantic-release": "semantic-release",
20 | "test:watch": "jest --watch",
21 | "test": "jest --coverage",
22 | "typecheck": "tsc --noEmit"
23 | },
24 | "repository": {
25 | "type": "git",
26 | "url": "git+https://github.com/flexcodelabs/sarufi.git"
27 | },
28 | "license": "MIT",
29 | "author": {
30 | "name": "Flexcode Labs",
31 | "email": "hi@flexcodelabs.com",
32 | "url": "https://github.com/flexcodelabs"
33 | },
34 | "engines": {
35 | "node": ">=12.0"
36 | },
37 | "keywords": [
38 | "sarufi",
39 | "sdk",
40 | "nodejs",
41 | "flexcodelabs",
42 | "flexcode labs"
43 | ],
44 | "bugs": {
45 | "url": "https://github.com/flexcodelabs/sarufi/issues"
46 | },
47 | "homepage": "https://github.com/flexcodelabs/sarufi#readme",
48 | "devDependencies": {
49 | "@ryansonshine/commitizen": "^4.2.8",
50 | "@ryansonshine/cz-conventional-changelog": "^3.3.4",
51 | "@types/jest": "^27.5.2",
52 | "@types/node": "^12.20.11",
53 | "@typescript-eslint/eslint-plugin": "^4.22.0",
54 | "@typescript-eslint/parser": "^4.22.0",
55 | "@vercel/ncc": "^0.36.1",
56 | "axios": "^1.4.0",
57 | "conventional-changelog-conventionalcommits": "^5.0.0",
58 | "eslint": "^7.25.0",
59 | "eslint-config-prettier": "^8.3.0",
60 | "eslint-plugin-node": "^11.1.0",
61 | "eslint-plugin-prettier": "^3.4.0",
62 | "http-request-mock": "^1.8.2",
63 | "husky": "^6.0.0",
64 | "jest": "^27.2.0",
65 | "lint-staged": "^10.5.4",
66 | "prettier": "^2.2.1",
67 | "semantic-release": "^19.0.2",
68 | "ts-jest": "^27.0.5",
69 | "ts-node": "^10.2.1",
70 | "typescript": "^4.2.4"
71 | },
72 | "config": {
73 | "commitizen": {
74 | "path": "./node_modules/@ryansonshine/cz-conventional-changelog"
75 | }
76 | },
77 | "lint-staged": {
78 | "*.ts": "eslint --cache --cache-location .eslintcache --fix"
79 | },
80 | "release": {
81 | "branches": [
82 | "main"
83 | ],
84 | "plugins": [
85 | [
86 | "@semantic-release/commit-analyzer",
87 | {
88 | "preset": "conventionalcommits",
89 | "releaseRules": [
90 | {
91 | "type": "build",
92 | "scope": "deps",
93 | "release": "patch"
94 | }
95 | ]
96 | }
97 | ],
98 | [
99 | "@semantic-release/release-notes-generator",
100 | {
101 | "preset": "conventionalcommits",
102 | "presetConfig": {
103 | "types": [
104 | {
105 | "type": "feat",
106 | "section": "Features"
107 | },
108 | {
109 | "type": "fix",
110 | "section": "Bug Fixes"
111 | },
112 | {
113 | "type": "build",
114 | "section": "Dependencies and Other Build Updates",
115 | "hidden": false
116 | }
117 | ]
118 | }
119 | }
120 | ],
121 | "@semantic-release/npm",
122 | "@semantic-release/github"
123 | ]
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as sarufi from './modules/instances';
2 | export default sarufi;
3 |
--------------------------------------------------------------------------------
/src/modules/chat.ts:
--------------------------------------------------------------------------------
1 | import { Bot } from '../shared/interfaces/bot.interface';
2 | import {
3 | ConversationInput,
4 | ConversationResponse,
5 | WhatsappConversationResponse,
6 | } from '../shared/interfaces/conversation.interface';
7 | import axios, { AxiosError } from 'axios';
8 | import { ErrorResponse } from '../shared/interfaces/shared.interface';
9 | import { sanitizeErrorResponse } from '../shared/helpers/error.helper';
10 | import { id } from '../shared/helpers/id.helper';
11 |
12 | export class ChatConversation {
13 | constructor(
14 | private bot: Bot,
15 | private url: string,
16 | private token: string | undefined
17 | ) {}
18 |
19 | /**
20 | * A method to start chat with your bot
21 | *
22 | * @param data with message and optional chat_id, if chat id is not supplied, a unique string will be used
23 | */
24 | chat = async (
25 | data: ConversationInput
26 | ): Promise<
27 | ConversationResponse | WhatsappConversationResponse | ErrorResponse
28 | > => {
29 | if (this.token) {
30 | return this.startChat(this.token, {
31 | ...data,
32 | channel: data.channel ?? 'general',
33 | });
34 | }
35 | return {
36 | success: false,
37 | bot: undefined,
38 | message: 'Unauthorized',
39 | };
40 | };
41 |
42 | private startChat = async (
43 | token: string,
44 | data: ConversationInput
45 | ): Promise<
46 | ConversationResponse | WhatsappConversationResponse | ErrorResponse
47 | > => {
48 | let url = `${this.url}/conversation`;
49 | console.log(data);
50 | if (data.channel?.toLowerCase() === 'whatsapp') {
51 | url = `${url}/whatsapp`;
52 | }
53 |
54 | try {
55 | const response: ConversationResponse | WhatsappConversationResponse = (
56 | await axios.post(
57 | url,
58 | {
59 | ...data,
60 | bot_id: this.bot.id,
61 | chat_id: data.chat_id || id,
62 | },
63 | { headers: { Authorization: `Bearer ${token}` } }
64 | )
65 | ).data;
66 | return { ...response, success: true };
67 | } catch (e) {
68 | return sanitizeErrorResponse(e as AxiosError);
69 | }
70 | };
71 | }
72 |
--------------------------------------------------------------------------------
/src/modules/instances.ts:
--------------------------------------------------------------------------------
1 | import { Login, LoginResponse } from '../shared/interfaces/auth.interface';
2 | import {
3 | BotResponse,
4 | BotsResponse,
5 | CreateBot,
6 | DeleteBot,
7 | GetBot,
8 | GetBots,
9 | UpdateBot,
10 | } from '../shared/interfaces/bot.interface';
11 | import {
12 | ChatInput,
13 | ConversationResponse,
14 | WhatsappConversationResponse,
15 | } from '../shared/interfaces/conversation.interface';
16 | import { ErrorResponse } from '../shared/interfaces/shared.interface';
17 | import { Sarufi } from './sarufi';
18 |
19 | /**
20 | *
21 | * @param data payload to login you in and interact with your bot, an api key from sarufi dashboard and an optional url
22 | */
23 | export const login = async (
24 | data: Login
25 | ): Promise => {
26 | const sarufi = new Sarufi(data.url);
27 | return await sarufi.login({
28 | api_key: data.api_key,
29 | });
30 | };
31 |
32 | /**
33 | * A method to create a new bot
34 | *
35 | * @param data payload to create a bot with bot property as the DTO, an optional url and an optional token
36 | */
37 |
38 | export const createBot = async (
39 | data: CreateBot
40 | ): Promise => {
41 | const sarufi = new Sarufi(data?.url, data?.api_key);
42 | return await sarufi.createBot(data.bot);
43 | };
44 |
45 | export const getBots = async (
46 | data?: GetBots
47 | ): Promise => {
48 | const sarufi = new Sarufi(data?.url, data?.api_key);
49 | return await sarufi.getBots();
50 | };
51 |
52 | export const getBot = async (
53 | data: GetBot
54 | ): Promise => {
55 | const sarufi = new Sarufi(data?.url, data?.api_key);
56 | return await sarufi.getBot(data.id);
57 | };
58 |
59 | export const updateBot = async (
60 | data: UpdateBot
61 | ): Promise => {
62 | const sarufi = new Sarufi(data?.url, data?.api_key);
63 | return await sarufi.updateBot(data.id, data.bot);
64 | };
65 |
66 | export const deleteBot = async (
67 | data: GetBot
68 | ): Promise => {
69 | const sarufi = new Sarufi(data?.url, data?.api_key);
70 | return await sarufi.deleteBot(data.id);
71 | };
72 |
73 | export const chat = async (
74 | data: ChatInput
75 | ): Promise<
76 | ErrorResponse | ConversationResponse | WhatsappConversationResponse
77 | > => {
78 | const sarufi = new Sarufi(data?.url, data?.api_key);
79 | return await sarufi.chat({
80 | message: data.message,
81 | bot_id: data.bot_id,
82 | chat_id: data.chat_id,
83 | channel: data.channel ?? 'general',
84 | });
85 | };
86 |
--------------------------------------------------------------------------------
/src/modules/sarufi.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosError } from 'axios';
2 | import { sanitizeErrorResponse } from '../shared/helpers/error.helper';
3 | import { id } from '../shared/helpers/id.helper';
4 | import { Login, LoginResponse } from '../shared/interfaces/auth.interface';
5 | import {
6 | BotRequest,
7 | BotResponse,
8 | BotsResponse,
9 | DeleteBot,
10 | } from '../shared/interfaces/bot.interface';
11 | import {
12 | ChatInput,
13 | ConversationInput,
14 | ConversationResponse,
15 | WhatsappConversationResponse,
16 | } from '../shared/interfaces/conversation.interface';
17 | import { ErrorResponse } from '../shared/interfaces/shared.interface';
18 | import { ChatConversation } from './chat';
19 |
20 | declare let global: {
21 | api_key: string | undefined;
22 | url: string | undefined;
23 | };
24 |
25 | export class Sarufi {
26 | constructor(private url?: string, private api_key?: string) {
27 | global.url = this.url;
28 | if (this.api_key) {
29 | global.api_key = this.api_key;
30 | }
31 | }
32 | private BASE_DOMAIN = global.url || 'https://developers.sarufi.io';
33 |
34 | login = async (data: Login): Promise => {
35 | if (!global?.api_key) {
36 | global.api_key = data.api_key;
37 | }
38 | return { api_key: data.api_key };
39 | // throw new Error(
40 | // 'Method deprecated. Get a token from the website dashboard'
41 | // );
42 | // try {
43 | // const loggedInUser: LoginResponse = (
44 | // await axios.post(`${this.BASE_DOMAIN}/api/api_key`, data)
45 | // ).data;
46 | // if (global) {
47 | // global.api_key = loggedInUser.api_key;
48 | // }
49 | // return { ...loggedInUser, success: true };
50 | // } catch (error) {
51 | // return sanitizeErrorResponse(error as AxiosError);
52 | // }
53 | };
54 |
55 | createBot = async (bot: BotRequest): Promise => {
56 | if (global?.api_key) {
57 | return await this.createUserBot(bot, global?.api_key);
58 | }
59 | return { success: false, bot: undefined, message: 'Unauthorized' };
60 | };
61 |
62 | getBots = async (): Promise => {
63 | if (global?.api_key) {
64 | return await this.getUserBots(global?.api_key);
65 | }
66 | return { success: false, bots: [], message: 'Unauthorized' };
67 | };
68 |
69 | getBot = async (id: number): Promise => {
70 | if (global?.api_key && id) {
71 | return await this.getUserBot(global?.api_key, id);
72 | }
73 | return {
74 | success: false,
75 | bots: undefined,
76 | message: global?.api_key ? 'Unauthorized' : 'Bot ID not supplied',
77 | };
78 | };
79 |
80 | updateBot = async (
81 | id: number,
82 | bot: BotRequest
83 | ): Promise => {
84 | if (global?.api_key && id) {
85 | return await this.updateUserBot(global?.api_key, id, bot);
86 | }
87 | return {
88 | success: false,
89 | bot: undefined,
90 | message: global?.api_key ? 'Unauthorized' : 'Bot ID not supplied',
91 | };
92 | };
93 |
94 | deleteBot = async (id: number): Promise => {
95 | if (global?.api_key && id) {
96 | return await this.deleteUserBot(global?.api_key, id);
97 | }
98 | return {
99 | success: false,
100 | bot: undefined,
101 | message: global?.api_key ? 'Unauthorized' : 'Bot ID not supplied',
102 | };
103 | };
104 |
105 | chat = async (
106 | data: ChatInput
107 | ): Promise<
108 | ConversationResponse | WhatsappConversationResponse | ErrorResponse
109 | > => {
110 | if (global.api_key) {
111 | return this.startChat(data, global.api_key);
112 | }
113 | return {
114 | success: false,
115 | bot: undefined,
116 | message: global?.api_key ? 'Unauthorized' : 'Bot ID not supplied',
117 | };
118 | };
119 |
120 | private startChat = async (
121 | data: ChatInput,
122 | api_key: string
123 | ): Promise<
124 | ConversationResponse | WhatsappConversationResponse | ErrorResponse
125 | > => {
126 | let url = `${this.BASE_DOMAIN}/conversation`;
127 | if (data.channel?.toLowerCase() === 'whatsapp') {
128 | url = `${url}/whatsapp`;
129 | }
130 |
131 | try {
132 | const response: ConversationResponse | WhatsappConversationResponse = (
133 | await axios.post(
134 | url,
135 | {
136 | ...data,
137 | chat_id: data.chat_id || id,
138 | },
139 | { headers: { Authorization: `Bearer ${api_key}` } }
140 | )
141 | ).data;
142 | return { ...response, success: true };
143 | } catch (e) {
144 | return sanitizeErrorResponse(e as AxiosError);
145 | }
146 | };
147 |
148 | private getUserBots = async (
149 | api_key: string
150 | ): Promise => {
151 | try {
152 | const bots = (
153 | await axios.get(`${this.BASE_DOMAIN}/chatbots`, {
154 | headers: { Authorization: `Bearer ${api_key}` },
155 | })
156 | ).data;
157 | return { success: true, bots };
158 | } catch (e) {
159 | return sanitizeErrorResponse(e as AxiosError);
160 | }
161 | };
162 |
163 | private getUserBot = async (
164 | api_key: string,
165 | id: number
166 | ): Promise => {
167 | try {
168 | const bot = (
169 | await axios.get(`${this.BASE_DOMAIN}/chatbot/${id}`, {
170 | headers: { Authorization: `Bearer ${api_key}` },
171 | })
172 | ).data;
173 | const chatBot = new ChatConversation(
174 | bot,
175 | this.BASE_DOMAIN,
176 | global.api_key
177 | );
178 | return {
179 | success: true,
180 | bot,
181 | chat: async function (
182 | data: ConversationInput
183 | ): Promise<
184 | ConversationResponse | WhatsappConversationResponse | ErrorResponse
185 | > {
186 | return await chatBot.chat(data);
187 | },
188 | };
189 | } catch (e) {
190 | return sanitizeErrorResponse(e as AxiosError);
191 | }
192 | };
193 |
194 | private updateUserBot = async (
195 | api_key: string,
196 | id: number,
197 | bot: BotRequest
198 | ): Promise => {
199 | try {
200 | const updatedBot = (
201 | await axios.put(`${this.BASE_DOMAIN}/chatbot/${id}`, bot, {
202 | headers: { Authorization: `Bearer ${api_key}` },
203 | })
204 | ).data;
205 | const chatBot = new ChatConversation(
206 | updatedBot,
207 | this.BASE_DOMAIN,
208 | global.api_key
209 | );
210 | return {
211 | success: true,
212 | bot: updatedBot,
213 | chat: async function (
214 | data: ConversationInput
215 | ): Promise<
216 | ConversationResponse | WhatsappConversationResponse | ErrorResponse
217 | > {
218 | return await chatBot.chat(data);
219 | },
220 | };
221 | } catch (e) {
222 | return sanitizeErrorResponse(e as AxiosError);
223 | }
224 | };
225 |
226 | private deleteUserBot = async (
227 | api_key: string,
228 | id: number
229 | ): Promise => {
230 | try {
231 | const deleteBot: DeleteBot = (
232 | await axios.delete(`${this.BASE_DOMAIN}/chatbot/${id}`, {
233 | headers: { Authorization: `Bearer ${api_key}` },
234 | })
235 | ).data;
236 | return { ...deleteBot, success: true };
237 | } catch (e) {
238 | return sanitizeErrorResponse(e as AxiosError);
239 | }
240 | };
241 |
242 | private createUserBot = async (
243 | bot: BotRequest,
244 | api_key: string
245 | ): Promise => {
246 | try {
247 | const createdBot = (
248 | await axios.post(`${this.BASE_DOMAIN}/chatbot`, bot, {
249 | headers: { Authorization: `Bearer ${api_key}` },
250 | })
251 | ).data;
252 | const chatBot = new ChatConversation(
253 | createdBot,
254 | this.BASE_DOMAIN,
255 | global.api_key
256 | );
257 |
258 | return {
259 | success: true,
260 | bot: createdBot,
261 | chat: async function (
262 | data: ConversationInput
263 | ): Promise<
264 | ConversationResponse | WhatsappConversationResponse | ErrorResponse
265 | > {
266 | return await chatBot.chat(data);
267 | },
268 | };
269 | } catch (e) {
270 | return sanitizeErrorResponse(e as AxiosError);
271 | }
272 | };
273 | }
274 |
--------------------------------------------------------------------------------
/src/shared/helpers/error.helper.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosError } from 'axios';
2 | import { ErrorResponse } from '../interfaces/shared.interface';
3 |
4 | const sanitizedAxiosError = (
5 | error: AxiosError,
6 | serverError: AxiosError
7 | ) => {
8 | if (serverError?.response) {
9 | return {
10 | ...serverError?.response?.data,
11 | message: error.message,
12 | error: error.message,
13 | code: serverError?.code || 'FAILED',
14 | status: serverError?.response?.status || 400,
15 | success: false,
16 | };
17 | }
18 | return {
19 | ...error,
20 | success: false,
21 | message: error?.message,
22 | token: '',
23 | status: 400,
24 | };
25 | };
26 | export const sanitizeErrorResponse = (error: AxiosError): ErrorResponse => {
27 | const serverError = error as AxiosError;
28 | if (axios.isAxiosError(error)) {
29 | return sanitizedAxiosError(error, serverError);
30 | }
31 | return {
32 | success: false,
33 | message: serverError?.message || 'Internal server error',
34 | token: '',
35 | status: 400,
36 | code: 'FAILED',
37 | };
38 | };
39 |
--------------------------------------------------------------------------------
/src/shared/helpers/id.helper.ts:
--------------------------------------------------------------------------------
1 | const sample = (id = [], fn = Math.random): string | undefined => {
2 | if (id.length === 0) return;
3 | return id[Math.round(fn() * (id.length - 1))];
4 | };
5 |
6 | const geratedId = (limit = 13, fn = Math.random): string => {
7 | const allowedLetters: any = [
8 | 'abcdefghijklmnopqrstuvwxyz',
9 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
10 | ].join('');
11 | const allowedCharacters: any = ['0123456789', allowedLetters].join('');
12 |
13 | const generatedId = [sample(allowedLetters, fn)];
14 |
15 | for (let i = 0; i < limit - 1; i++) {
16 | generatedId.push(sample(allowedCharacters, fn));
17 | }
18 |
19 | return generatedId.join('');
20 | };
21 |
22 | export const id = geratedId();
23 |
--------------------------------------------------------------------------------
/src/shared/interfaces/auth.interface.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * An interface for login response
3 | */
4 | export interface LoginResponse {
5 | api_key: string;
6 | }
7 |
8 | /**
9 | * An interface for login request
10 | */
11 | export interface Login {
12 | api_key: string;
13 | url?: string;
14 | }
15 |
--------------------------------------------------------------------------------
/src/shared/interfaces/bot.interface.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ConversationInput,
3 | ConversationResponse,
4 | WhatsappConversationResponse,
5 | } from './conversation.interface';
6 | import { ErrorResponse } from './shared.interface';
7 |
8 | /**
9 | * Get bots response
10 | */
11 | export interface BotsResponse {
12 | success: boolean;
13 | bots: Bot[];
14 | }
15 |
16 | /**
17 | * An interface for bot intents used for training bots
18 | */
19 | export interface Intents {
20 | [key: string]: string | string[] | unknown | number | Record;
21 | }
22 |
23 | /**
24 | * An interface for bot flows used for training bots
25 | */
26 | export interface Flows {
27 | [key: string]: string | string[] | unknown | number | Record;
28 | }
29 |
30 | /**
31 | * An interface for both get bot and create bot responses
32 | */
33 | export interface BotResponse {
34 | success: boolean;
35 | bot: Bot | undefined;
36 | /**
37 | * @method Chat method for initiating chat with a particular bot
38 | */
39 | chat: (
40 | data: ConversationInput
41 | ) => Promise<
42 | ConversationResponse | WhatsappConversationResponse | ErrorResponse
43 | >;
44 | }
45 | /**
46 | * A sarufi single bot interface with all the properties
47 | */
48 | export interface Bot {
49 | id: number;
50 | user_id: number;
51 | name: string;
52 | description: string;
53 | visible_on_community: boolean;
54 | intents: Intents;
55 | flows: Flows;
56 | model_name: string;
57 | industry: string;
58 | created_at: string;
59 | updated_at: string;
60 | language?: string;
61 | }
62 |
63 | export interface BotRequest {
64 | name: string;
65 | description?: string;
66 | intents?: Intents;
67 | flows?: Flows;
68 | model_name?: string;
69 | industry?: string;
70 | visible_on_community: boolean;
71 | language?: string;
72 | }
73 |
74 | export interface DeleteBot {
75 | success: string;
76 | message: string;
77 | }
78 |
79 | export interface GetBot {
80 | id: number;
81 | url?: string;
82 | api_key?: string;
83 | }
84 | export interface GetBots {
85 | api_key?: string;
86 | url?: string;
87 | }
88 | export interface UpdateBot {
89 | id: number;
90 | bot: BotRequest;
91 | url?: string;
92 | api_key?: string;
93 | }
94 |
95 | /**
96 | * An interface for create bot request
97 | */
98 | export interface CreateBot {
99 | bot: BotRequest;
100 | url?: string;
101 | api_key?: string;
102 | }
103 |
--------------------------------------------------------------------------------
/src/shared/interfaces/conversation.interface.ts:
--------------------------------------------------------------------------------
1 | export interface Conversation {
2 | chat_id: string | number | unknown;
3 | bot_id: string;
4 | message: string;
5 | message_type?: string;
6 | current_state?: string;
7 | next_state?: string;
8 | language?: string;
9 | }
10 |
11 | export interface ConversationResponse {
12 | message:
13 | | string
14 | | number
15 | | unknown
16 | | [string]
17 | | Record
18 | | [Record];
19 | success: boolean;
20 | memory?: {
21 | [key: string]: string | unknown;
22 | };
23 | [key: string]: string | unknown;
24 | }
25 |
26 | export interface WhatsappConversationResponse extends ConversationResponse {
27 | actions: Array<{
28 | send_message?: any[];
29 | send_button?: any;
30 | send_reply_button?: { type: string; body: { text: string }; action: any };
31 | send_image?: Array<{ link: string; caption: string }>;
32 | send_audio?: any;
33 | send_videos?: any;
34 | }>;
35 | }
36 |
37 | export interface ConversationInput {
38 | message: string;
39 | chat_id?: string | number | unknown;
40 | channel?: 'general' | 'whatsapp';
41 | }
42 |
43 | export interface ChatInput {
44 | message: string;
45 | bot_id: string | number;
46 | chat_id?: string | number | unknown;
47 | url?: string;
48 | api_key?: string;
49 | channel?: 'general' | 'whatsapp';
50 | }
51 |
--------------------------------------------------------------------------------
/src/shared/interfaces/shared.interface.ts:
--------------------------------------------------------------------------------
1 | import { Bot } from './bot.interface';
2 | import {
3 | ConversationInput,
4 | ConversationResponse,
5 | WhatsappConversationResponse,
6 | } from './conversation.interface';
7 |
8 | export interface ErrorResponse {
9 | success: boolean;
10 | message: string;
11 | code?: string;
12 | status?: number;
13 | token?: string | undefined;
14 | bot?: Bot | undefined;
15 | bots?: Bot[] | undefined;
16 | chat?: (
17 | data: ConversationInput
18 | ) => Promise;
19 | [key: string]: unknown;
20 | }
21 |
--------------------------------------------------------------------------------
/test/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'axios/lib/adapters/xhr';
2 |
--------------------------------------------------------------------------------
/test/mocks/mocks.constants.ts:
--------------------------------------------------------------------------------
1 | export const BotData = {
2 | name: 'Bennett',
3 | user_id: 16,
4 | description: 'PUT DESCRIPTION HERE',
5 | industry: 'general',
6 | intents: {},
7 | flows: {},
8 | visible_on_community: false,
9 | updated_at: '2022-09-26T23=21=50.496431',
10 | id: 27,
11 | model_name: 'models/079d4fa7f3159d44b540c5dd3f146591.pkl',
12 | created_at: '2022-09-26T23=21=50.496419',
13 | };
14 |
15 | export const BotsData = [
16 | {
17 | intents: {},
18 | user_id: 16,
19 | description: 'PUT DESCRIPTION HERE',
20 | industry: 'general',
21 | updated_at: '2022-09-26T23:21:50.496431',
22 | name: 'Bennett',
23 | visible_on_community: false,
24 | flows: {},
25 | id: 26,
26 | model_name: 'models/079d4fa7f3159d44b540c5dd3f146591.pkl',
27 | created_at: '2022-09-26T23:21:50.496419',
28 | },
29 | {
30 | intents: {},
31 | user_id: 16,
32 | description: 'PUT DESCRIPTION HERE',
33 | industry: 'general',
34 | visible_on_community: false,
35 | updated_at: '2022-09-26T23:21:50.496431',
36 | name: 'Bennett',
37 | flows: {},
38 | id: 27,
39 | model_name: 'models/7051f2f6512bd89d29082c5c683289cb.pkl',
40 | created_at: '2022-09-26T23:21:50.496419',
41 | },
42 | ];
43 |
44 | export const BotUpdate = {
45 | name: "Bennett's Bot",
46 | user_id: 16,
47 | description: 'PUT DESCRIPTION HERE',
48 | industry: 'general',
49 | visible_on_community: false,
50 | intents: {},
51 | flows: {
52 | greets: [
53 | 'hey',
54 | 'hello',
55 | 'hi',
56 | 'howdy',
57 | 'hola',
58 | 'greetings',
59 | 'good morning',
60 | 'good afternoon',
61 | 'good evening',
62 | ],
63 | },
64 | id: 26,
65 | model_name: 'models/079d4fa7f3159d44b540c5dd3f146591.pkl',
66 | };
67 |
--------------------------------------------------------------------------------
/test/modules/index.spec.ts:
--------------------------------------------------------------------------------
1 | import sarufi from '../../src';
2 | import { LoginResponse } from '../../src/shared/interfaces/auth.interface';
3 | import axios from 'axios';
4 | import xhrAdapter from 'axios/lib/adapters/xhr';
5 | import HttpRequestMock from 'http-request-mock';
6 | import { ErrorResponse } from '../../src/shared/interfaces/shared.interface';
7 | import { Bot, BotRequest } from '../../src/shared/interfaces/bot.interface';
8 | import { BotData, BotsData, BotUpdate } from '../mocks/mocks.constants';
9 | import {
10 | ConversationResponse,
11 | WhatsappConversationResponse,
12 | } from '../../src/shared/interfaces/conversation.interface';
13 |
14 | jest.setTimeout(300000);
15 |
16 | axios.defaults.adapter = xhrAdapter;
17 | const mocker = HttpRequestMock.setupForUnitTest('xhr');
18 | const BASE_DOMAIN = 'https://developers.sarufi.io';
19 |
20 | // describe('Login User', () => {
21 | // const loginDTO = {
22 | // token_type: 'Bearer',
23 | // scope: 'read:data write:data',
24 | // expires_in: 86400,
25 | // api_key: 'basic token',
26 | // };
27 |
28 | // mocker.post(`${BASE_DOMAIN}/api/access_token`, ():
29 | // | LoginResponse
30 | // | ErrorResponse => {
31 | // return loginDTO;
32 | // });
33 | // it('Should return message and token', async () => {
34 | // const result: LoginResponse | ErrorResponse = await sarufi.login({
35 | // client_id: '',
36 | // client_secret: '',
37 | // });
38 | // expect(result.api_key).toBeDefined();
39 | // expect(result).toMatchObject(loginDTO);
40 | // });
41 | // });
42 |
43 | describe('Create Bot', () => {
44 | mocker.post(`${BASE_DOMAIN}/chatbot`, (): BotRequest => {
45 | return BotData;
46 | });
47 | it('Should return a created bot given a valid token', async () => {
48 | const createdBot = await sarufi.createBot({
49 | bot: { name: '', visible_on_community: false },
50 | });
51 | expect(createdBot.success).toBe(true);
52 | expect(createdBot.bot).toMatchObject(BotData);
53 | });
54 | });
55 |
56 | describe('Get Bots', () => {
57 | mocker.get(`${BASE_DOMAIN}/chatbots`, (): Bot[] => {
58 | return BotsData;
59 | });
60 | it('Should return user bots', async () => {
61 | const userBots = await sarufi.getBots();
62 | expect(userBots.success).toBe(true);
63 | expect(userBots.bots?.length).toBe(2);
64 | });
65 | });
66 |
67 | describe('Get Bot', () => {
68 | mocker.get(`${BASE_DOMAIN}/chatbot/27`, (): Bot => {
69 | return BotData;
70 | });
71 | it('Should return a single bot', async () => {
72 | const userBot = await sarufi.getBot({ id: BotData.id });
73 | expect(userBot.success).toBe(true);
74 | expect(userBot.bot?.id).toBe(27);
75 | });
76 | });
77 |
78 | describe('Update Bot', () => {
79 | mocker.put(`${BASE_DOMAIN}/chatbot/27`, (): BotRequest => {
80 | return BotUpdate;
81 | });
82 | it('Should return an updated bot', async () => {
83 | const bot: BotRequest = {
84 | name: "Bennett's Bot",
85 | description: 'PUT DESCRIPTION HERE',
86 | industry: 'general',
87 | visible_on_community: false,
88 | intents: {
89 | greets: [
90 | 'hey',
91 | 'hello',
92 | 'hi',
93 | 'howdy',
94 | 'hola',
95 | 'greetings',
96 | 'good morning',
97 | 'good afternoon',
98 | 'good evening',
99 | ],
100 | goodbye: [
101 | 'bye',
102 | 'goodbye',
103 | 'see you later',
104 | 'see you soon',
105 | 'see you',
106 | 'talk to you later',
107 | 'talk to you soon',
108 | 'talk to you',
109 | ],
110 | order_pizza: [
111 | 'I need a pizza',
112 | 'I want a pizza',
113 | 'order a pizza',
114 | 'I want to order a pizza',
115 | ],
116 | },
117 | model_name: 'models/079d4fa7f3159d44b540c5dd3f146591.pkl',
118 | };
119 | const userBot = await sarufi.updateBot({ id: BotData.id, bot });
120 | expect(userBot.success).toBe(true);
121 | expect(userBot.bot?.id).toBe(26);
122 | });
123 | });
124 |
125 | describe('Start chat with bot Bot', () => {
126 | mocker.post(`${BASE_DOMAIN}/conversation`, (): ConversationResponse => {
127 | return {
128 | success: true,
129 | message: ['Hi, How are you?'],
130 | memory: {
131 | greets: 'oya',
132 | },
133 | next_state: 'end',
134 | };
135 | });
136 | it('Should start a chat with a bot', async () => {
137 | const chat = await sarufi.chat({
138 | message: 'Hey',
139 | bot_id: 27,
140 | chat_id: 'HEYOO',
141 | });
142 | expect(chat.success).toBe(true);
143 | expect(chat?.message).toBeDefined();
144 | });
145 | });
146 | describe('Delete Bot', () => {
147 | mocker.delete(`${BASE_DOMAIN}/chatbot/27`, (): { message: string } => {
148 | return { message: 'Bot deleted' };
149 | });
150 | it('Should delete a bot', async () => {
151 | const deleteBot = await sarufi.deleteBot({ id: BotData.id });
152 | expect(deleteBot.success).toBe(true);
153 | expect(deleteBot?.message).toBe('Bot deleted');
154 | });
155 | });
156 | describe('Start conversation', () => {
157 | mocker.get(`${BASE_DOMAIN}/chatbot/27`, (): Bot => {
158 | return BotData;
159 | });
160 | it('Should start convo given bot', async () => {
161 | const userBot = await sarufi.getBot({ id: BotData.id });
162 | expect(userBot.success).toBe(true);
163 | expect(userBot.bot?.id).toBe(27);
164 | if (userBot.chat) {
165 | const convoResponse = {
166 | success: true,
167 | message: ['Hi, How are you?'],
168 | memory: {
169 | greets: 'oya',
170 | },
171 | next_state: 'end',
172 | };
173 | mocker.post(`${BASE_DOMAIN}/conversation`, (): ConversationResponse => {
174 | return convoResponse;
175 | });
176 | const convo:
177 | | ConversationResponse
178 | | WhatsappConversationResponse
179 | | ErrorResponse = await userBot.chat({
180 | message: 'Yooh',
181 | chat_id: 'Start',
182 | });
183 | expect(convo).toMatchObject(convoResponse);
184 | expect(convo?.memory).toMatchObject(convoResponse.memory);
185 | }
186 | });
187 | });
188 |
--------------------------------------------------------------------------------
/test/shared/error.helper.spec.ts:
--------------------------------------------------------------------------------
1 | import { sanitizeErrorResponse } from '../../src/shared/helpers/error.helper';
2 | import { AxiosError } from 'axios';
3 | describe('Login', () => {
4 | it('Should return a sanitized Error', () => {
5 | const error = {
6 | message: 'An error occured',
7 | response: { data: {} },
8 | code: 'FAILED',
9 | status: 404,
10 | };
11 | const errorResponse = sanitizeErrorResponse(error as unknown as AxiosError);
12 | expect(errorResponse.message).toBeDefined();
13 | expect(errorResponse.success).toBe(false);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/test/shared/id.helper.spec.ts:
--------------------------------------------------------------------------------
1 | import { id } from '../../src/shared/helpers/id.helper';
2 |
3 | describe('ID', () => {
4 | it('Should return a random id', () => {
5 | const chart_id = id;
6 | expect(chart_id).toBeDefined();
7 | expect(chart_id.length).toBe(13);
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Basic Options */
6 | // "incremental": true, /* Enable incremental compilation */
7 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
9 | // "lib": [], /* Specify library files to be included in the compilation. */
10 | // "allowJs": true, /* Allow javascript files to be compiled. */
11 | // "checkJs": true, /* Report errors in .js files. */
12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
13 | "declaration": true, /* Generates corresponding '.d.ts' file. */
14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
15 | // "sourceMap": true, /* Generates corresponding '.map' file. */
16 | // "outFile": "./", /* Concatenate and emit output to single file. */
17 | "outDir": "./lib/", /* Redirect output structure to the directory. */
18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
19 | // "composite": true, /* Enable project compilation */
20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
21 | // "removeComments": true, /* Do not emit comments to output. */
22 | // "noEmit": true, /* Do not emit outputs. */
23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
26 |
27 | /* Strict Type-Checking Options */
28 | "strict": true, /* Enable all strict type-checking options. */
29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
30 | // "strictNullChecks": true, /* Enable strict null checks. */
31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
36 |
37 | /* Additional Checks */
38 | // "noUnusedLocals": true, /* Report errors on unused locals. */
39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
43 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
44 |
45 | /* Module Resolution Options */
46 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
47 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
48 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
50 | // "typeRoots": [], /* List of folders to include type definitions from. */
51 | // "types": [], /* Type declaration files to be included in compilation. */
52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
53 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
55 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
56 |
57 | /* Source Map Options */
58 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
60 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
61 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
62 |
63 | /* Experimental Options */
64 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
65 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
66 |
67 | /* Advanced Options */
68 | "skipLibCheck": true, /* Skip type checking of declaration files. */
69 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
70 | },
71 | "include": ["src/**/*.ts", "test/**/*.ts"],
72 | "exclude": ["*.spec.ts"]
73 | }
74 |
--------------------------------------------------------------------------------