@automate.ax/integration-contracts 0.145.1 → 0.146.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/dist/calcom/openapi-schemas.generated.json +76 -20
- package/dist/calcom/openapi-types.generated.d.ts +8 -8
- package/dist/highlevel/api.d.ts +43 -0
- package/dist/highlevel/api.js +236 -0
- package/dist/highlevel/events.d.ts +1148 -0
- package/dist/highlevel/events.js +303 -0
- package/dist/highlevel/index.d.ts +6 -0
- package/dist/highlevel/index.js +4 -0
- package/dist/highlevel/openapi-operations.generated.json +10122 -0
- package/dist/highlevel/openapi-types.generated.d.ts +9158 -0
- package/dist/highlevel/openapi-types.generated.js +1 -0
- package/dist/highlevel/operation-manifest.d.ts +608 -0
- package/dist/highlevel/operation-manifest.js +98 -0
- package/dist/highlevel/schemas.d.ts +41 -0
- package/dist/highlevel/schemas.js +39 -0
- package/dist/highlevel/types.d.ts +41 -0
- package/dist/highlevel/types.js +1 -0
- package/dist/resend/action-schemas.d.ts +2 -2
- package/dist/triggers.d.ts +2 -1
- package/package.json +9 -2
- package/src/calcom/openapi-schemas.generated.json +76 -20
- package/src/calcom/openapi-types.generated.ts +8 -8
- package/src/highlevel/api.ts +294 -0
- package/src/highlevel/events.ts +355 -0
- package/src/highlevel/index.ts +6 -0
- package/src/highlevel/openapi-operations.generated.json +10122 -0
- package/src/highlevel/openapi-types.generated.ts +9928 -0
- package/src/highlevel/operation-manifest.ts +740 -0
- package/src/highlevel/schemas.ts +85 -0
- package/src/highlevel/types.ts +91 -0
- package/src/triggers.ts +2 -0
|
@@ -945,13 +945,27 @@
|
|
|
945
945
|
"example": 60
|
|
946
946
|
},
|
|
947
947
|
"eventTypeId": {
|
|
948
|
-
"
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
948
|
+
"anyOf": [
|
|
949
|
+
{
|
|
950
|
+
"type": "number",
|
|
951
|
+
"example": 50,
|
|
952
|
+
"deprecated": true,
|
|
953
|
+
"description": "Deprecated - rely on 'eventType' object containing the id instead."
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
"type": "null"
|
|
957
|
+
}
|
|
958
|
+
]
|
|
952
959
|
},
|
|
953
960
|
"eventType": {
|
|
954
|
-
"
|
|
961
|
+
"anyOf": [
|
|
962
|
+
{
|
|
963
|
+
"$ref": "#/$defs/EventType"
|
|
964
|
+
},
|
|
965
|
+
{
|
|
966
|
+
"type": "null"
|
|
967
|
+
}
|
|
968
|
+
]
|
|
955
969
|
},
|
|
956
970
|
"meetingUrl": {
|
|
957
971
|
"type": "string",
|
|
@@ -5666,13 +5680,27 @@
|
|
|
5666
5680
|
"example": 60
|
|
5667
5681
|
},
|
|
5668
5682
|
"eventTypeId": {
|
|
5669
|
-
"
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5683
|
+
"anyOf": [
|
|
5684
|
+
{
|
|
5685
|
+
"type": "number",
|
|
5686
|
+
"example": 50,
|
|
5687
|
+
"deprecated": true,
|
|
5688
|
+
"description": "Deprecated - rely on 'eventType' object containing the id instead."
|
|
5689
|
+
},
|
|
5690
|
+
{
|
|
5691
|
+
"type": "null"
|
|
5692
|
+
}
|
|
5693
|
+
]
|
|
5673
5694
|
},
|
|
5674
5695
|
"eventType": {
|
|
5675
|
-
"
|
|
5696
|
+
"anyOf": [
|
|
5697
|
+
{
|
|
5698
|
+
"$ref": "#/$defs/EventType"
|
|
5699
|
+
},
|
|
5700
|
+
{
|
|
5701
|
+
"type": "null"
|
|
5702
|
+
}
|
|
5703
|
+
]
|
|
5676
5704
|
},
|
|
5677
5705
|
"meetingUrl": {
|
|
5678
5706
|
"type": "string",
|
|
@@ -5898,13 +5926,27 @@
|
|
|
5898
5926
|
"example": 60
|
|
5899
5927
|
},
|
|
5900
5928
|
"eventTypeId": {
|
|
5901
|
-
"
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5929
|
+
"anyOf": [
|
|
5930
|
+
{
|
|
5931
|
+
"type": "number",
|
|
5932
|
+
"example": 50,
|
|
5933
|
+
"deprecated": true,
|
|
5934
|
+
"description": "Deprecated - rely on 'eventType' object containing the id instead."
|
|
5935
|
+
},
|
|
5936
|
+
{
|
|
5937
|
+
"type": "null"
|
|
5938
|
+
}
|
|
5939
|
+
]
|
|
5905
5940
|
},
|
|
5906
5941
|
"eventType": {
|
|
5907
|
-
"
|
|
5942
|
+
"anyOf": [
|
|
5943
|
+
{
|
|
5944
|
+
"$ref": "#/$defs/EventType"
|
|
5945
|
+
},
|
|
5946
|
+
{
|
|
5947
|
+
"type": "null"
|
|
5948
|
+
}
|
|
5949
|
+
]
|
|
5908
5950
|
},
|
|
5909
5951
|
"meetingUrl": {
|
|
5910
5952
|
"type": "string",
|
|
@@ -7823,13 +7865,27 @@
|
|
|
7823
7865
|
"example": 60
|
|
7824
7866
|
},
|
|
7825
7867
|
"eventTypeId": {
|
|
7826
|
-
"
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7868
|
+
"anyOf": [
|
|
7869
|
+
{
|
|
7870
|
+
"type": "number",
|
|
7871
|
+
"example": 50,
|
|
7872
|
+
"deprecated": true,
|
|
7873
|
+
"description": "Deprecated - rely on 'eventType' object containing the id instead."
|
|
7874
|
+
},
|
|
7875
|
+
{
|
|
7876
|
+
"type": "null"
|
|
7877
|
+
}
|
|
7878
|
+
]
|
|
7830
7879
|
},
|
|
7831
7880
|
"eventType": {
|
|
7832
|
-
"
|
|
7881
|
+
"anyOf": [
|
|
7882
|
+
{
|
|
7883
|
+
"$ref": "#/$defs/EventType"
|
|
7884
|
+
},
|
|
7885
|
+
{
|
|
7886
|
+
"type": "null"
|
|
7887
|
+
}
|
|
7888
|
+
]
|
|
7833
7889
|
},
|
|
7834
7890
|
"meetingUrl": {
|
|
7835
7891
|
"type": "string",
|
|
@@ -679,8 +679,8 @@ export interface components {
|
|
|
679
679
|
* @example
|
|
680
680
|
* 50
|
|
681
681
|
*/
|
|
682
|
-
eventTypeId: number;
|
|
683
|
-
eventType: components["schemas"]["EventType"];
|
|
682
|
+
eventTypeId: number | null;
|
|
683
|
+
eventType: components["schemas"]["EventType"] | null;
|
|
684
684
|
/**
|
|
685
685
|
* Format: uri
|
|
686
686
|
*
|
|
@@ -3827,8 +3827,8 @@ export interface components {
|
|
|
3827
3827
|
* @example
|
|
3828
3828
|
* 50
|
|
3829
3829
|
*/
|
|
3830
|
-
eventTypeId: number;
|
|
3831
|
-
eventType: components["schemas"]["EventType"];
|
|
3830
|
+
eventTypeId: number | null;
|
|
3831
|
+
eventType: components["schemas"]["EventType"] | null;
|
|
3832
3832
|
/**
|
|
3833
3833
|
* Format: uri
|
|
3834
3834
|
*
|
|
@@ -4010,8 +4010,8 @@ export interface components {
|
|
|
4010
4010
|
* @example
|
|
4011
4011
|
* 50
|
|
4012
4012
|
*/
|
|
4013
|
-
eventTypeId: number;
|
|
4014
|
-
eventType: components["schemas"]["EventType"];
|
|
4013
|
+
eventTypeId: number | null;
|
|
4014
|
+
eventType: components["schemas"]["EventType"] | null;
|
|
4015
4015
|
/**
|
|
4016
4016
|
* Format: uri
|
|
4017
4017
|
*
|
|
@@ -5510,8 +5510,8 @@ export interface components {
|
|
|
5510
5510
|
* @example
|
|
5511
5511
|
* 50
|
|
5512
5512
|
*/
|
|
5513
|
-
eventTypeId: number;
|
|
5514
|
-
eventType: components["schemas"]["EventType"];
|
|
5513
|
+
eventTypeId: number | null;
|
|
5514
|
+
eventType: components["schemas"]["EventType"] | null;
|
|
5515
5515
|
/**
|
|
5516
5516
|
* Format: uri
|
|
5517
5517
|
*
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type Encodable } from "@automate.ax/codec";
|
|
2
|
+
import type { HighLevelOperationKey } from "./operation-manifest.js";
|
|
3
|
+
import type { HighLevelOperationInput, HighLevelOperationOutput } from "./types.js";
|
|
4
|
+
/** Resolved account accepted by the shared HighLevel API client. */
|
|
5
|
+
export interface HighLevelResolvedAccount {
|
|
6
|
+
connectionMethodId: string;
|
|
7
|
+
secret: Record<string, unknown>;
|
|
8
|
+
serviceId: "highlevel";
|
|
9
|
+
}
|
|
10
|
+
/** Structured error returned by a rejected HighLevel request. */
|
|
11
|
+
export declare class HighLevelApiError extends Error {
|
|
12
|
+
readonly body?: Encodable;
|
|
13
|
+
readonly retryAfter?: string;
|
|
14
|
+
readonly status: number;
|
|
15
|
+
/**
|
|
16
|
+
* Creates an error from one rejected HighLevel response.
|
|
17
|
+
*
|
|
18
|
+
* @param options - Rejected response details.
|
|
19
|
+
* @param options.body - Encodable provider error body.
|
|
20
|
+
* @param options.retryAfter - Provider retry timing.
|
|
21
|
+
* @param options.status - HTTP status.
|
|
22
|
+
*/
|
|
23
|
+
constructor(options: {
|
|
24
|
+
body?: Encodable;
|
|
25
|
+
retryAfter?: string;
|
|
26
|
+
status: number;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Creates an authenticated client for the frozen HighLevel action surface.
|
|
31
|
+
*
|
|
32
|
+
* @param account - Resolved HighLevel account.
|
|
33
|
+
* @throws When the connection method or secret is invalid.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getHighLevelApi(account: HighLevelResolvedAccount): {
|
|
36
|
+
/**
|
|
37
|
+
* Executes and validates one named HighLevel OpenAPI operation.
|
|
38
|
+
*
|
|
39
|
+
* @param key - Frozen operation key.
|
|
40
|
+
* @param input - Flattened operation input.
|
|
41
|
+
*/
|
|
42
|
+
operation<TKey extends HighLevelOperationKey>(key: TKey, input: HighLevelOperationInput<TKey>): Promise<HighLevelOperationOutput<TKey>>;
|
|
43
|
+
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { encodableSchema } from "@automate.ax/codec";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { highLevelOperation, highLevelOperationOutputSchema } from "./schemas.js";
|
|
4
|
+
const HIGH_LEVEL_API_BASE_URL = "https://services.leadconnectorhq.com/";
|
|
5
|
+
const HIGH_LEVEL_API_ORIGIN = new URL(HIGH_LEVEL_API_BASE_URL).origin;
|
|
6
|
+
const HIGH_LEVEL_SECRET_SCHEMA = z.object({
|
|
7
|
+
accessToken: z.string().min(1),
|
|
8
|
+
locationId: z.string().min(1),
|
|
9
|
+
});
|
|
10
|
+
/** Structured error returned by a rejected HighLevel request. */
|
|
11
|
+
export class HighLevelApiError extends Error {
|
|
12
|
+
body;
|
|
13
|
+
retryAfter;
|
|
14
|
+
status;
|
|
15
|
+
/**
|
|
16
|
+
* Creates an error from one rejected HighLevel response.
|
|
17
|
+
*
|
|
18
|
+
* @param options - Rejected response details.
|
|
19
|
+
* @param options.body - Encodable provider error body.
|
|
20
|
+
* @param options.retryAfter - Provider retry timing.
|
|
21
|
+
* @param options.status - HTTP status.
|
|
22
|
+
*/
|
|
23
|
+
constructor(options) {
|
|
24
|
+
super(getErrorMessage(options.body) ??
|
|
25
|
+
`HighLevel API request failed with status ${options.status}.`);
|
|
26
|
+
this.name = "HighLevelApiError";
|
|
27
|
+
this.body = options.body;
|
|
28
|
+
this.retryAfter = options.retryAfter;
|
|
29
|
+
this.status = options.status;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Creates an authenticated client for the frozen HighLevel action surface.
|
|
34
|
+
*
|
|
35
|
+
* @param account - Resolved HighLevel account.
|
|
36
|
+
* @throws When the connection method or secret is invalid.
|
|
37
|
+
*/
|
|
38
|
+
export function getHighLevelApi(account) {
|
|
39
|
+
const credentials = HIGH_LEVEL_SECRET_SCHEMA.parse(account.secret);
|
|
40
|
+
if (account.connectionMethodId !== "oauth" &&
|
|
41
|
+
account.connectionMethodId !== "private-integration-token") {
|
|
42
|
+
throw new Error(`Unsupported HighLevel connection method: ${account.connectionMethodId}`);
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
/**
|
|
46
|
+
* Executes and validates one named HighLevel OpenAPI operation.
|
|
47
|
+
*
|
|
48
|
+
* @param key - Frozen operation key.
|
|
49
|
+
* @param input - Flattened operation input.
|
|
50
|
+
*/
|
|
51
|
+
async operation(key, input) {
|
|
52
|
+
const definition = highLevelOperation(key);
|
|
53
|
+
const pathValues = new Map();
|
|
54
|
+
const queryValues = new Map();
|
|
55
|
+
const bodyValues = {};
|
|
56
|
+
const inputRecord = input;
|
|
57
|
+
for (const name of definition.pathParameters) {
|
|
58
|
+
pathValues.set(name, inputRecord[name]);
|
|
59
|
+
}
|
|
60
|
+
for (const { name } of definition.queryParameters) {
|
|
61
|
+
queryValues.set(name, inputRecord[name]);
|
|
62
|
+
}
|
|
63
|
+
for (const name of definition.bodyParameters) {
|
|
64
|
+
if (inputRecord[name] !== undefined)
|
|
65
|
+
bodyValues[name] = inputRecord[name];
|
|
66
|
+
}
|
|
67
|
+
for (const placement of definition.locationPlacements) {
|
|
68
|
+
if (placement === "path")
|
|
69
|
+
pathValues.set("locationId", credentials.locationId);
|
|
70
|
+
if (placement === "query")
|
|
71
|
+
queryValues.set("locationId", credentials.locationId);
|
|
72
|
+
if (placement === "body")
|
|
73
|
+
bodyValues.locationId = credentials.locationId;
|
|
74
|
+
}
|
|
75
|
+
// Resolve path placeholders before applying the fixed-origin guard.
|
|
76
|
+
const path = definition.path.replace(/\{([^}]+)\}/g, (_match, name) => encodePathSegment(pathValues.get(name), name));
|
|
77
|
+
const normalizedPath = path.replace(/^\/+/, "");
|
|
78
|
+
const url = new URL(normalizedPath, HIGH_LEVEL_API_BASE_URL);
|
|
79
|
+
if (!normalizedPath ||
|
|
80
|
+
normalizedPath.includes("://") ||
|
|
81
|
+
normalizedPath.includes("\\") ||
|
|
82
|
+
normalizedPath.includes("?") ||
|
|
83
|
+
normalizedPath.includes("#") ||
|
|
84
|
+
url.origin !== HIGH_LEVEL_API_ORIGIN) {
|
|
85
|
+
throw new TypeError("HighLevel API paths must remain provider-relative.");
|
|
86
|
+
}
|
|
87
|
+
for (const parameter of definition.queryParameters) {
|
|
88
|
+
appendQuery(url, { ...parameter, name: parameter.providerName }, queryValues.get(parameter.name));
|
|
89
|
+
}
|
|
90
|
+
if (definition.locationPlacements.includes("query")) {
|
|
91
|
+
url.searchParams.set(definition.locationParameterNames.query ?? "locationId", credentials.locationId);
|
|
92
|
+
}
|
|
93
|
+
const hasBody = definition.bodyParameters.length > 0 ||
|
|
94
|
+
definition.locationPlacements.includes("body");
|
|
95
|
+
const response = await fetch(url, {
|
|
96
|
+
body: hasBody ? JSON.stringify(bodyValues) : undefined,
|
|
97
|
+
headers: {
|
|
98
|
+
Accept: "application/json",
|
|
99
|
+
Authorization: `Bearer ${credentials.accessToken}`,
|
|
100
|
+
Version: definition.apiVersion,
|
|
101
|
+
...(hasBody ? { "Content-Type": "application/json" } : {}),
|
|
102
|
+
},
|
|
103
|
+
method: definition.method,
|
|
104
|
+
redirect: "error",
|
|
105
|
+
});
|
|
106
|
+
const parsed = response.ok && definition.responseMode === "binary"
|
|
107
|
+
? {
|
|
108
|
+
contentType: response.headers.get("Content-Type") ??
|
|
109
|
+
"application/octet-stream",
|
|
110
|
+
data: Buffer.from(await response.arrayBuffer()).toString("base64"),
|
|
111
|
+
}
|
|
112
|
+
: await parseResponseText(response);
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
const body = encodableSchema.safeParse(parsed);
|
|
115
|
+
throw new HighLevelApiError({
|
|
116
|
+
...(body.success && { body: body.data }),
|
|
117
|
+
retryAfter: getRetryAfter(response.headers),
|
|
118
|
+
status: response.status,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return highLevelOperationOutputSchema(key).parse(parsed);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Encodes one required path parameter.
|
|
127
|
+
*
|
|
128
|
+
* @param value - Parameter value.
|
|
129
|
+
* @param name - Parameter name.
|
|
130
|
+
* @throws When the value is not scalar.
|
|
131
|
+
*/
|
|
132
|
+
function encodePathSegment(value, name) {
|
|
133
|
+
if (typeof value !== "number" && typeof value !== "string") {
|
|
134
|
+
throw new TypeError(`HighLevel path parameter ${name} is required.`);
|
|
135
|
+
}
|
|
136
|
+
return encodeURIComponent(String(value));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Appends one OpenAPI query parameter.
|
|
140
|
+
*
|
|
141
|
+
* @param url - Provider request URL.
|
|
142
|
+
* @param parameter - Query serialization metadata.
|
|
143
|
+
* @param parameter.explode - Whether array items repeat the query key.
|
|
144
|
+
* @param parameter.name - Query parameter name.
|
|
145
|
+
* @param parameter.style - OpenAPI serialization style.
|
|
146
|
+
* @param value - Parameter value.
|
|
147
|
+
* @throws When the value is not scalar or an array of scalars.
|
|
148
|
+
*/
|
|
149
|
+
function appendQuery(url, parameter, value) {
|
|
150
|
+
if (value == null)
|
|
151
|
+
return;
|
|
152
|
+
if (Array.isArray(value)) {
|
|
153
|
+
if (parameter.style === "form" && parameter.explode) {
|
|
154
|
+
for (const item of value)
|
|
155
|
+
url.searchParams.append(parameter.name, stringifyQueryValue(item));
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
url.searchParams.set(parameter.name, value.map(stringifyQueryValue).join(","));
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
url.searchParams.set(parameter.name, stringifyQueryValue(value));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Converts one supported query value to text.
|
|
166
|
+
*
|
|
167
|
+
* @param value - Query value.
|
|
168
|
+
* @throws When the value is not scalar.
|
|
169
|
+
*/
|
|
170
|
+
function stringifyQueryValue(value) {
|
|
171
|
+
if (typeof value !== "boolean" &&
|
|
172
|
+
typeof value !== "number" &&
|
|
173
|
+
typeof value !== "string") {
|
|
174
|
+
throw new TypeError("HighLevel query parameters must be scalar.");
|
|
175
|
+
}
|
|
176
|
+
return String(value);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Reads provider-directed retry timing.
|
|
180
|
+
*
|
|
181
|
+
* @param headers - Rejected response headers.
|
|
182
|
+
*/
|
|
183
|
+
function getRetryAfter(headers) {
|
|
184
|
+
const retryAfter = headers.get("Retry-After");
|
|
185
|
+
if (retryAfter)
|
|
186
|
+
return retryAfter;
|
|
187
|
+
const interval = headers.get("X-RateLimit-Interval-Milliseconds");
|
|
188
|
+
if (!interval || !/^\d+$/.test(interval))
|
|
189
|
+
return undefined;
|
|
190
|
+
return String(Math.ceil(Number(interval) / 1_000));
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Extracts a provider error message.
|
|
194
|
+
*
|
|
195
|
+
* @param value - Encodable provider error body.
|
|
196
|
+
*/
|
|
197
|
+
function getErrorMessage(value) {
|
|
198
|
+
if (!isPlainObject(value))
|
|
199
|
+
return undefined;
|
|
200
|
+
for (const key of ["message", "error", "error_description"]) {
|
|
201
|
+
const candidate = value[key];
|
|
202
|
+
if (typeof candidate === "string" && candidate.trim())
|
|
203
|
+
return candidate;
|
|
204
|
+
}
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Narrows a value to a plain object.
|
|
209
|
+
*
|
|
210
|
+
* @param value - Candidate value.
|
|
211
|
+
*/
|
|
212
|
+
function isPlainObject(value) {
|
|
213
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Parses JSON while retaining non-JSON provider bodies.
|
|
217
|
+
*
|
|
218
|
+
* @param value - Response body text.
|
|
219
|
+
*/
|
|
220
|
+
function parseJson(value) {
|
|
221
|
+
try {
|
|
222
|
+
return JSON.parse(value);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return { response: value };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Reads one text or JSON provider response.
|
|
230
|
+
*
|
|
231
|
+
* @param response - Provider response to decode.
|
|
232
|
+
*/
|
|
233
|
+
async function parseResponseText(response) {
|
|
234
|
+
const text = await response.text();
|
|
235
|
+
return text ? parseJson(text) : null;
|
|
236
|
+
}
|