n8n-nodes-zoho-desk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,339 @@
1
+ # n8n-nodes-zoho-desk-extended
2
+
3
+ Production-ready n8n community node for integrating with [Zoho Desk](https://www.zoho.com/desk/) API. Manage support tickets, contacts, and accounts with comprehensive field support, dynamic resource loading, and automatic contact creation.
4
+
5
+ [![npm version](https://badge.fury.io/js/n8n-nodes-zoho-desk-extended.svg)](https://www.npmjs.com/package/n8n-nodes-zoho-desk-extended)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Features
9
+
10
+ - **Full CRUD Operations**: Create, Read, Update, Delete for Tickets, Contacts, and Accounts
11
+ - **Dynamic Dropdowns**: Department & Team selection auto-populated from your Zoho Desk account
12
+ - **Automatic Contact Creation**: Provide email/name and contacts are auto-created or matched
13
+ - **Pagination Support**: "Return All" toggle to fetch all records automatically
14
+ - **Comprehensive Field Support**: All fields including custom fields, priority, due dates, and more
15
+ - **OAuth2 Authentication**: Secure authentication with support for all Zoho data centers
16
+ - **Type-Safe & Validated**: Full TypeScript implementation with input validation
17
+ - **Fully Tested**: 17 integration tests covering all operations
18
+
19
+ ## Supported Operations
20
+
21
+ | Resource | Operations |
22
+ |----------|------------|
23
+ | **Ticket** | Create, Get, List, Update, Delete, Add Comment, List Threads |
24
+ | **Contact** | Create, Get, List, Update, Delete |
25
+ | **Account** | Create, Get, List, Update, Delete |
26
+
27
+ ## Installation
28
+
29
+ ### Community Node (Recommended)
30
+
31
+ 1. Go to **Settings** > **Community Nodes** in n8n
32
+ 2. Search for `n8n-nodes-zoho-desk-extended`
33
+ 3. Click **Install**
34
+
35
+ ### Manual Installation
36
+
37
+ ```bash
38
+ npm install n8n-nodes-zoho-desk-extended
39
+ ```
40
+
41
+ ## Setup
42
+
43
+ ### 1. Create Zoho Desk OAuth2 Client
44
+
45
+ 1. Go to [Zoho API Console](https://api-console.zoho.com/)
46
+ 2. Click on "Add Client"
47
+ 3. Choose "Server-based Applications"
48
+ 4. Enter the following details:
49
+ - **Client Name**: n8n Integration
50
+ - **Homepage URL**: Your n8n instance URL
51
+ - **Authorized Redirect URIs**: `https://your-n8n-instance.com/rest/oauth2-credential/callback`
52
+ 5. Click "Create"
53
+ 6. Note down your **Client ID** and **Client Secret**
54
+
55
+ ### 2. Get Organization ID
56
+
57
+ 1. Login to your Zoho Desk account
58
+ 2. Go to **Setup** > **Developer Space** > **API**
59
+ 3. Copy your **Organization ID**
60
+
61
+ ### 3. Configure Credentials in n8n
62
+
63
+ 1. In n8n, go to **Credentials** > **New**
64
+ 2. Select **Zoho Desk OAuth2 API**
65
+ 3. Enter:
66
+ - **Client ID**: From step 1
67
+ - **Client Secret**: From step 1
68
+ - **Organization ID**: From step 2
69
+ - **Data Center**: Select your Zoho data center
70
+ 4. Click **Connect** and authorize the application
71
+
72
+ ## Operations
73
+
74
+ ### Ticket Operations
75
+
76
+ #### Create Ticket
77
+
78
+ Creates a new support ticket with automatic contact creation/matching.
79
+
80
+ **Required Fields:**
81
+ - **Department**: Select from dropdown (auto-populated)
82
+ - **Subject**: Ticket subject line
83
+
84
+ **Primary Fields:**
85
+ - **Priority**: Low, Medium, or High (default: Medium)
86
+ - **Classification**: Question, Problem, Request, Others (default: Question)
87
+ - **Due Date**: Resolution deadline
88
+ - **Description**: Detailed ticket description
89
+ - **Team**: Select from department's teams (dropdown)
90
+
91
+ **Contact** (optional):
92
+ - **Email** OR **Last Name** (at least one required if providing contact)
93
+ - First Name, Phone (optional)
94
+ - If email exists, existing contact is used; otherwise new contact is created
95
+
96
+ **Additional Fields:**
97
+ - Account ID, Assignee ID, Category, Channel, Custom Fields, Email, Language, Phone, Product ID, Resolution, Secondary Contacts, Status, Sub Category
98
+
99
+ #### Get Ticket
100
+
101
+ Retrieves a single ticket by ID.
102
+
103
+ #### List Tickets
104
+
105
+ Lists all tickets with optional filters.
106
+ - **Return All**: Toggle to fetch all tickets (with automatic pagination)
107
+ - **Limit**: Maximum number of results (when Return All is off)
108
+ - **Filters**: Department ID, Assignee ID, Status
109
+
110
+ #### Update Ticket
111
+
112
+ Updates an existing ticket. All fields are optional.
113
+
114
+ #### Delete Ticket
115
+
116
+ Moves a ticket to trash.
117
+
118
+ #### Add Comment
119
+
120
+ Adds a comment to a ticket.
121
+ - **Content**: Comment text
122
+ - **Is Public**: Whether visible to customers or internal only
123
+
124
+ #### List Threads
125
+
126
+ Lists all conversations/threads on a ticket.
127
+
128
+ ### Contact Operations
129
+
130
+ #### Create Contact
131
+
132
+ **Required Fields:**
133
+ - **Last Name**
134
+ - **Email**
135
+
136
+ **Additional Fields:**
137
+ - First Name, Phone, Mobile, Account ID, Twitter, Facebook, Type, Description, Custom Fields
138
+
139
+ #### Get / List / Update / Delete Contact
140
+
141
+ Standard CRUD operations with pagination support for List.
142
+
143
+ ### Account Operations
144
+
145
+ #### Create Account
146
+
147
+ **Required Fields:**
148
+ - **Account Name**
149
+
150
+ **Additional Fields:**
151
+ - Website, Phone, Fax, Industry, Description, Code, City, Country, State, Street, Zip, Custom Fields
152
+
153
+ #### Get / List / Update / Delete Account
154
+
155
+ Standard CRUD operations with pagination support for List.
156
+
157
+ ## Usage Examples
158
+
159
+ ### Create Ticket with Contact Auto-Creation
160
+
161
+ ```json
162
+ {
163
+ "departmentId": "1892000000006907",
164
+ "subject": "Order processing delay",
165
+ "contact": {
166
+ "email": "carol@zylker.com",
167
+ "lastName": "Carol",
168
+ "firstName": "Lucas",
169
+ "phone": "1 888 900 9646"
170
+ },
171
+ "description": "Customer experiencing delays in order processing",
172
+ "dueDate": "2025-12-01T10:00:00.000Z",
173
+ "priority": "High",
174
+ "classification": "Problem",
175
+ "teamId": "8920000000069071"
176
+ }
177
+ ```
178
+
179
+ ### Create Contact
180
+
181
+ ```json
182
+ {
183
+ "lastName": "Smith",
184
+ "email": "john.smith@example.com",
185
+ "firstName": "John",
186
+ "phone": "+1-555-123-4567",
187
+ "description": "VIP Customer"
188
+ }
189
+ ```
190
+
191
+ ### Create Account
192
+
193
+ ```json
194
+ {
195
+ "accountName": "Acme Corporation",
196
+ "website": "https://acme.example.com",
197
+ "industry": "Technology",
198
+ "phone": "+1-555-000-0000",
199
+ "city": "San Francisco",
200
+ "country": "USA"
201
+ }
202
+ ```
203
+
204
+ ### List All Tickets with Filters
205
+
206
+ ```json
207
+ {
208
+ "returnAll": true,
209
+ "filters": {
210
+ "status": "Open",
211
+ "departmentId": "1892000000006907"
212
+ }
213
+ }
214
+ ```
215
+
216
+ ## Field Details
217
+
218
+ ### Custom Fields (cf)
219
+
220
+ Pass custom fields as a JSON object:
221
+
222
+ ```json
223
+ {
224
+ "cf": {
225
+ "cf_fieldname": "value",
226
+ "cf_priority_level": "urgent",
227
+ "cf_product_version": "2.0.1"
228
+ }
229
+ }
230
+ ```
231
+
232
+ ### Secondary Contacts
233
+
234
+ Provide multiple contact IDs as comma-separated values:
235
+
236
+ ```
237
+ "secondaryContacts": "1892000000042038, 1892000000042042, 1892000000042056"
238
+ ```
239
+
240
+ ## Validation & Error Handling
241
+
242
+ The node includes comprehensive validation:
243
+
244
+ - **Contact Validation**: Ensures either email or lastName is provided
245
+ - **ID Validation**: Validates IDs are numeric with proper length
246
+ - **JSON Validation**: Safe parsing of custom fields with detailed error messages
247
+ - **Email Validation**: RFC 5322 compliant email validation
248
+ - **Clear Error Messages**: User-friendly error messages with actionable guidance
249
+
250
+ ## API Rate Limits
251
+
252
+ Zoho Desk API has the following rate limits:
253
+ - 10 requests per second per organization
254
+ - 5000 API calls per day
255
+
256
+ The node detects rate limiting (HTTP 429) and provides clear error messages.
257
+
258
+ ## Supported Zoho Data Centers
259
+
260
+ - zoho.com (US)
261
+ - zoho.com.au (Australia)
262
+ - zoho.com.cn (China)
263
+ - zoho.eu (EU)
264
+ - zoho.in (India)
265
+ - zoho.jp (Japan)
266
+
267
+ ## Development
268
+
269
+ ### Setup
270
+
271
+ ```bash
272
+ # Clone the repository
273
+ git clone https://github.com/ron137/zoho-desk-n8n-integration.git
274
+
275
+ # Install dependencies
276
+ npm install
277
+
278
+ # Build the node
279
+ npm run build
280
+
281
+ # Run in development mode
282
+ npm run dev
283
+
284
+ # Run linting
285
+ npm run lint
286
+
287
+ # Run tests
288
+ npm run test:integration
289
+ ```
290
+
291
+ ### Testing
292
+
293
+ The project includes comprehensive integration tests that run against the real Zoho Desk API:
294
+
295
+ ```bash
296
+ # Create .env.test with your credentials
297
+ ZOHO_DESK_ACCESS_TOKEN=your_token
298
+ ZOHO_DESK_REFRESH_TOKEN=your_refresh_token
299
+ ZOHO_DESK_CLIENT_ID=your_client_id
300
+ ZOHO_DESK_CLIENT_SECRET=your_client_secret
301
+ ZOHO_DESK_ORG_ID=your_org_id
302
+ ZOHO_DESK_DATACENTER=com
303
+
304
+ # Run tests
305
+ npm run test:integration
306
+ ```
307
+
308
+ ## License
309
+
310
+ MIT
311
+
312
+ ## Support
313
+
314
+ If you encounter any issues or have questions:
315
+ 1. Check the [Issues](https://github.com/ron137/zoho-desk-n8n-integration/issues) page
316
+ 2. Create a new issue if your problem isn't already listed
317
+
318
+ ## Changelog
319
+
320
+ ### 1.0.0 - Major Release
321
+ - **New Resources**: Contact and Account with full CRUD operations
322
+ - **New Ticket Operations**: Get, List, Delete, Add Comment, List Threads
323
+ - **Pagination**: "Return All" toggle with automatic page fetching
324
+ - **Integration Tests**: 17 comprehensive tests for all operations
325
+ - **Lint Compliance**: Full n8n linting rules compliance
326
+ - **Bug Fixes**: Proper error handling with NodeOperationError/ApplicationError
327
+
328
+ ### Previous Versions (as @enthu/n8n-nodes-zoho-desk)
329
+ - 0.3.x - Ticket create/update operations
330
+ - 0.2.x - Dynamic dropdowns, contact auto-creation
331
+ - 0.1.x - Initial release
332
+
333
+ ## Resources
334
+
335
+ - [Zoho Desk API Documentation](https://desk.zoho.com/DeskAPIDocument)
336
+ - [n8n Documentation](https://docs.n8n.io/)
337
+ - [n8n Community Nodes](https://docs.n8n.io/integrations/community-nodes/)
338
+ - [GitHub Repository](https://github.com/ron137/zoho-desk-n8n-integration)
339
+ - [npm Package](https://www.npmjs.com/package/n8n-nodes-zoho-desk-extended)
@@ -0,0 +1,11 @@
1
+ import { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare class ZohoDeskOAuth2Api implements ICredentialType {
3
+ name: string;
4
+ extends: string[];
5
+ displayName: string;
6
+ documentationUrl: string;
7
+ properties: INodeProperties[];
8
+ test: ICredentialTestRequest;
9
+ authenticate: IAuthenticateGeneric;
10
+ }
11
+ //# sourceMappingURL=ZohoDeskOAuth2Api.credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZohoDeskOAuth2Api.credentials.d.ts","sourceRoot":"","sources":["../../credentials/ZohoDeskOAuth2Api.credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,EACf,eAAe,EAChB,MAAM,cAAc,CAAC;AAEtB,qBAAa,iBAAkB,YAAW,eAAe;IACvD,IAAI,SAAuB;IAC3B,OAAO,WAAiB;IACxB,WAAW,SAA0B;IACrC,gBAAgB,SAA0D;IAC1E,UAAU,EAAE,eAAe,EAAE,CAwF3B;IAEF,IAAI,EAAE,sBAAsB,CAQ1B;IAEF,YAAY,EAAE,oBAAoB,CAOhC;CACH"}
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ZohoDeskOAuth2Api = void 0;
4
+ class ZohoDeskOAuth2Api {
5
+ constructor() {
6
+ this.name = 'zohoDeskOAuth2Api';
7
+ this.extends = ['oAuth2Api'];
8
+ this.displayName = 'Zoho Desk OAuth2 API';
9
+ this.documentationUrl = 'https://desk.zoho.com/support/APIDocument#OAuthtoken';
10
+ this.properties = [
11
+ {
12
+ displayName: 'Grant Type',
13
+ name: 'grantType',
14
+ type: 'hidden',
15
+ default: 'authorizationCode',
16
+ },
17
+ {
18
+ displayName: 'Zoho Data Center',
19
+ name: 'datacenter',
20
+ type: 'options',
21
+ options: [
22
+ {
23
+ name: 'zoho.com (US)',
24
+ value: 'com',
25
+ },
26
+ {
27
+ name: 'zoho.com.au (Australia)',
28
+ value: 'com.au',
29
+ },
30
+ {
31
+ name: 'zoho.com.cn (China)',
32
+ value: 'com.cn',
33
+ },
34
+ {
35
+ name: 'zoho.eu (EU)',
36
+ value: 'eu',
37
+ },
38
+ {
39
+ name: 'zoho.in (India)',
40
+ value: 'in',
41
+ },
42
+ {
43
+ name: 'zoho.jp (Japan)',
44
+ value: 'jp',
45
+ },
46
+ ],
47
+ default: 'com',
48
+ description: 'The data center where your Zoho Desk account is hosted',
49
+ },
50
+ {
51
+ displayName: 'Organization ID',
52
+ name: 'orgId',
53
+ type: 'string',
54
+ default: '',
55
+ required: true,
56
+ description: 'Your Zoho Desk Organization ID. You can find this in Setup > Developer Space > API.',
57
+ },
58
+ {
59
+ displayName: 'Authorization URL',
60
+ name: 'authUrl',
61
+ type: 'hidden',
62
+ default: '=https://accounts.zoho.{{$self["datacenter"]}}/oauth/v2/auth',
63
+ required: true,
64
+ },
65
+ {
66
+ displayName: 'Access Token URL',
67
+ name: 'accessTokenUrl',
68
+ type: 'hidden',
69
+ default: '=https://accounts.zoho.{{$self["datacenter"]}}/oauth/v2/token',
70
+ required: true,
71
+ },
72
+ {
73
+ displayName: 'Base URL',
74
+ name: 'baseUrl',
75
+ type: 'hidden',
76
+ default: '=https://desk.zoho.{{$self["datacenter"]}}/api/v1',
77
+ },
78
+ {
79
+ displayName: 'Scope',
80
+ name: 'scope',
81
+ type: 'hidden',
82
+ default: 'Desk.tickets.ALL Desk.contacts.ALL Desk.accounts.ALL Desk.basic.READ Desk.settings.READ',
83
+ },
84
+ {
85
+ displayName: 'Auth URI Query Parameters',
86
+ name: 'authQueryParameters',
87
+ type: 'hidden',
88
+ default: 'access_type=offline&prompt=consent',
89
+ },
90
+ {
91
+ displayName: 'Authentication',
92
+ name: 'authentication',
93
+ type: 'hidden',
94
+ default: 'header',
95
+ },
96
+ ];
97
+ this.test = {
98
+ request: {
99
+ baseURL: '=https://desk.zoho.{{$credentials["datacenter"]}}/api/v1',
100
+ url: '/tickets?limit=1',
101
+ headers: {
102
+ orgId: '={{$credentials["orgId"]}}',
103
+ },
104
+ },
105
+ };
106
+ this.authenticate = {
107
+ type: 'generic',
108
+ properties: {
109
+ headers: {
110
+ Authorization: '=Bearer {{$credentials.oauthTokenData.access_token}}',
111
+ },
112
+ },
113
+ };
114
+ }
115
+ }
116
+ exports.ZohoDeskOAuth2Api = ZohoDeskOAuth2Api;
117
+ // Test comment for pre-commit hook verification
118
+ //# sourceMappingURL=ZohoDeskOAuth2Api.credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZohoDeskOAuth2Api.credentials.js","sourceRoot":"","sources":["../../credentials/ZohoDeskOAuth2Api.credentials.ts"],"names":[],"mappings":";;;AAOA,MAAa,iBAAiB;IAA9B;QACE,SAAI,GAAG,mBAAmB,CAAC;QAC3B,YAAO,GAAG,CAAC,WAAW,CAAC,CAAC;QACxB,gBAAW,GAAG,sBAAsB,CAAC;QACrC,qBAAgB,GAAG,sDAAsD,CAAC;QAC1E,eAAU,GAAsB;YAC9B;gBACE,WAAW,EAAE,YAAY;gBACzB,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,mBAAmB;aAC7B;YACD;gBACE,WAAW,EAAE,kBAAkB;gBAC/B,IAAI,EAAE,YAAY;gBAClB,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,eAAe;wBACrB,KAAK,EAAE,KAAK;qBACb;oBACD;wBACE,IAAI,EAAE,yBAAyB;wBAC/B,KAAK,EAAE,QAAQ;qBAChB;oBACD;wBACE,IAAI,EAAE,qBAAqB;wBAC3B,KAAK,EAAE,QAAQ;qBAChB;oBACD;wBACE,IAAI,EAAE,cAAc;wBACpB,KAAK,EAAE,IAAI;qBACZ;oBACD;wBACE,IAAI,EAAE,iBAAiB;wBACvB,KAAK,EAAE,IAAI;qBACZ;oBACD;wBACE,IAAI,EAAE,iBAAiB;wBACvB,KAAK,EAAE,IAAI;qBACZ;iBACF;gBACD,OAAO,EAAE,KAAK;gBACd,WAAW,EAAE,wDAAwD;aACtE;YACD;gBACE,WAAW,EAAE,iBAAiB;gBAC9B,IAAI,EAAE,OAAO;gBACb,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,IAAI;gBACd,WAAW,EACT,qFAAqF;aACxF;YACD;gBACE,WAAW,EAAE,mBAAmB;gBAChC,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,8DAA8D;gBACvE,QAAQ,EAAE,IAAI;aACf;YACD;gBACE,WAAW,EAAE,kBAAkB;gBAC/B,IAAI,EAAE,gBAAgB;gBACtB,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,+DAA+D;gBACxE,QAAQ,EAAE,IAAI;aACf;YACD;gBACE,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,mDAAmD;aAC7D;YACD;gBACE,WAAW,EAAE,OAAO;gBACpB,IAAI,EAAE,OAAO;gBACb,IAAI,EAAE,QAAQ;gBACd,OAAO,EACL,yFAAyF;aAC5F;YACD;gBACE,WAAW,EAAE,2BAA2B;gBACxC,IAAI,EAAE,qBAAqB;gBAC3B,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,oCAAoC;aAC9C;YACD;gBACE,WAAW,EAAE,gBAAgB;gBAC7B,IAAI,EAAE,gBAAgB;gBACtB,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,QAAQ;aAClB;SACF,CAAC;QAEF,SAAI,GAA2B;YAC7B,OAAO,EAAE;gBACP,OAAO,EAAE,0DAA0D;gBACnE,GAAG,EAAE,kBAAkB;gBACvB,OAAO,EAAE;oBACP,KAAK,EAAE,4BAA4B;iBACpC;aACF;SACF,CAAC;QAEF,iBAAY,GAAyB;YACnC,IAAI,EAAE,SAAS;YACf,UAAU,EAAE;gBACV,OAAO,EAAE;oBACP,aAAa,EAAE,sDAAsD;iBACtE;aACF;SACF,CAAC;IACJ,CAAC;CAAA;AAjHD,8CAiHC;AAED,gDAAgD"}
@@ -0,0 +1,20 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription, ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
2
+ export declare class ZohoDesk implements INodeType {
3
+ description: INodeTypeDescription;
4
+ methods: {
5
+ loadOptions: {
6
+ /**
7
+ * Load all departments from Zoho Desk
8
+ * @returns Array of department options for dropdown
9
+ */
10
+ getDepartments(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
11
+ /**
12
+ * Load teams for a specific department from Zoho Desk
13
+ * @returns Array of team options for dropdown, or empty array if department not selected
14
+ */
15
+ getTeams(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
16
+ };
17
+ };
18
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
19
+ }
20
+ //# sourceMappingURL=ZohoDesk.node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZohoDesk.node.d.ts","sourceRoot":"","sources":["../../../nodes/ZohoDesk/ZohoDesk.node.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,iBAAiB,EACjB,kBAAkB,EAClB,SAAS,EACT,oBAAoB,EAEpB,qBAAqB,EACrB,oBAAoB,EAErB,MAAM,cAAc,CAAC;AAsmBtB,qBAAa,QAAS,YAAW,SAAS;IACxC,WAAW,EAAE,oBAAoB,CAk5C/B;IAEF,OAAO;;YAEH;;;eAGG;iCACwB,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;YAiDlF;;;eAGG;2BACkB,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;;MAqE9E;IAEI,OAAO,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;CAqvBxE"}