@uipath/conversational-tool 1.202.1-preview.217 → 1.202.1-preview.238
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/index.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
metadata,
|
|
4
4
|
registerCommands
|
|
5
|
-
} from "./tool-
|
|
5
|
+
} from "./tool-p92sytgg.js";
|
|
6
6
|
import"./tool-nkyhbf79.js";
|
|
7
7
|
import {
|
|
8
8
|
Command
|
|
9
|
-
} from "./tool-
|
|
9
|
+
} from "./tool-71f0nc7a.js";
|
|
10
10
|
import"./tool-mpvp5rqs.js";
|
|
11
11
|
import"./tool-1yh8g550.js";
|
|
12
12
|
|
|
@@ -2100,7 +2100,7 @@ var require_commander = __commonJS(function(exports) {
|
|
|
2100
2100
|
var package_default = {
|
|
2101
2101
|
name: "@uipath/conversational-tool",
|
|
2102
2102
|
license: "SEE LICENSE IN LICENSE.txt",
|
|
2103
|
-
version: "1.202.1-preview.
|
|
2103
|
+
version: "1.202.1-preview.238",
|
|
2104
2104
|
description: "Handle conversations with deployed UiPath conversational processes.",
|
|
2105
2105
|
publishConfig: {
|
|
2106
2106
|
registry: "https://npm.pkg.github.com/"
|
|
@@ -4843,7 +4843,165 @@ class FilterImplicitLimitError extends Error {
|
|
|
4843
4843
|
this.instructions = "Pass --limit <n> to choose how many records the filter applies to. " + "To filter over all records, pass the command's maximum accepted --limit (see the option's description in --help).";
|
|
4844
4844
|
}
|
|
4845
4845
|
}
|
|
4846
|
-
function
|
|
4846
|
+
function countRecords(data) {
|
|
4847
|
+
if (Array.isArray(data))
|
|
4848
|
+
return data.length;
|
|
4849
|
+
return data == null ? 0 : 1;
|
|
4850
|
+
}
|
|
4851
|
+
function availableFields(data) {
|
|
4852
|
+
const records = Array.isArray(data) ? data : [data];
|
|
4853
|
+
const keys = new Set;
|
|
4854
|
+
for (const record of records) {
|
|
4855
|
+
if (record && typeof record === "object" && !Array.isArray(record)) {
|
|
4856
|
+
for (const key of Object.keys(record))
|
|
4857
|
+
keys.add(key);
|
|
4858
|
+
}
|
|
4859
|
+
}
|
|
4860
|
+
return [...keys];
|
|
4861
|
+
}
|
|
4862
|
+
function decodeQuotedIdentifier(whole, inner) {
|
|
4863
|
+
const [error, decoded] = catchError(() => JSON.parse(whole));
|
|
4864
|
+
if (!error && typeof decoded === "string")
|
|
4865
|
+
return decoded;
|
|
4866
|
+
return inner.replace(/\\(.)/g, "$1");
|
|
4867
|
+
}
|
|
4868
|
+
function referencedFields(filter) {
|
|
4869
|
+
const top = new Set;
|
|
4870
|
+
const nested = new Set;
|
|
4871
|
+
const withoutValueLiterals = filter.replace(/'(?:\\.|[^'\\])*'/g, " ").replace(/`(?:\\.|[^`\\])*`/g, " ");
|
|
4872
|
+
const isPathTail = (text, index) => text[index - 1] === "." && /[A-Za-z0-9_"]/.test(text[index - 2] ?? "");
|
|
4873
|
+
const quoted = [];
|
|
4874
|
+
const withoutLiterals = withoutValueLiterals.replace(/"((?:\\.|[^"\\])*)"/g, (whole, name, index) => {
|
|
4875
|
+
const isAlias = /^\s*:/.test(withoutValueLiterals.slice(index + whole.length));
|
|
4876
|
+
if (!isAlias) {
|
|
4877
|
+
quoted.push({
|
|
4878
|
+
name: decodeQuotedIdentifier(whole, name),
|
|
4879
|
+
index,
|
|
4880
|
+
tail: isPathTail(withoutValueLiterals, index)
|
|
4881
|
+
});
|
|
4882
|
+
}
|
|
4883
|
+
return `"${" ".repeat(whole.length - 2)}"`;
|
|
4884
|
+
});
|
|
4885
|
+
const descent = /[A-Za-z0-9_"]\s*[.[]/.exec(withoutLiterals);
|
|
4886
|
+
const rootEnd = descent ? descent.index + 1 : Number.POSITIVE_INFINITY;
|
|
4887
|
+
const place = (name, index, tail) => {
|
|
4888
|
+
if (tail || index >= rootEnd)
|
|
4889
|
+
nested.add(name);
|
|
4890
|
+
else
|
|
4891
|
+
top.add(name);
|
|
4892
|
+
};
|
|
4893
|
+
for (const entry of quoted)
|
|
4894
|
+
place(entry.name, entry.index, entry.tail);
|
|
4895
|
+
const identifiers = withoutLiterals.matchAll(/([A-Za-z_][A-Za-z0-9_]*)(\s*[:(])?/g);
|
|
4896
|
+
const reserved = new Set(["true", "false", "null", "and", "or", "not"]);
|
|
4897
|
+
for (const match of identifiers) {
|
|
4898
|
+
const [, name, trailing] = match;
|
|
4899
|
+
if (trailing || reserved.has(name))
|
|
4900
|
+
continue;
|
|
4901
|
+
place(name, match.index, isPathTail(withoutLiterals, match.index));
|
|
4902
|
+
}
|
|
4903
|
+
return {
|
|
4904
|
+
top: [...top],
|
|
4905
|
+
nested: [...nested].filter((name) => !top.has(name)),
|
|
4906
|
+
hasPredicate: /\[\s*\?/.test(withoutValueLiterals)
|
|
4907
|
+
};
|
|
4908
|
+
}
|
|
4909
|
+
function appearsBelowTopLevel(data, name) {
|
|
4910
|
+
let budget = 2000;
|
|
4911
|
+
const visit = (value, depth) => {
|
|
4912
|
+
if (budget-- <= 0 || depth > 4)
|
|
4913
|
+
return false;
|
|
4914
|
+
if (value === null || typeof value !== "object")
|
|
4915
|
+
return false;
|
|
4916
|
+
if (Array.isArray(value))
|
|
4917
|
+
return value.some((entry) => visit(entry, depth));
|
|
4918
|
+
const record = value;
|
|
4919
|
+
if (depth > 0 && Object.hasOwn(record, name))
|
|
4920
|
+
return true;
|
|
4921
|
+
return Object.values(record).some((entry) => visit(entry, depth + 1));
|
|
4922
|
+
};
|
|
4923
|
+
return visit(data, 0);
|
|
4924
|
+
}
|
|
4925
|
+
function alwaysNullKeys(records) {
|
|
4926
|
+
const objects = records.filter((r) => r != null && typeof r === "object" && !Array.isArray(r));
|
|
4927
|
+
if (objects.length === 0 || objects.length !== records.length)
|
|
4928
|
+
return [];
|
|
4929
|
+
const keys = [...new Set(objects.flatMap((o) => Object.keys(o)))];
|
|
4930
|
+
if (keys.length === 0)
|
|
4931
|
+
return [];
|
|
4932
|
+
const nullKeys = keys.filter((key) => objects.every((record) => record[key] === null));
|
|
4933
|
+
return nullKeys.length === keys.length ? nullKeys : [];
|
|
4934
|
+
}
|
|
4935
|
+
function carriesOnlyNulls(value) {
|
|
4936
|
+
if (value === null)
|
|
4937
|
+
return true;
|
|
4938
|
+
if (Array.isArray(value)) {
|
|
4939
|
+
return value.length > 0 && value.every(carriesOnlyNulls);
|
|
4940
|
+
}
|
|
4941
|
+
if (typeof value === "object" && value !== undefined) {
|
|
4942
|
+
const values = Object.values(value);
|
|
4943
|
+
return values.length > 0 && values.every(carriesOnlyNulls);
|
|
4944
|
+
}
|
|
4945
|
+
return false;
|
|
4946
|
+
}
|
|
4947
|
+
function nameList(names, cap2 = 12) {
|
|
4948
|
+
const shown = names.slice(0, cap2).map((n) => `\`${n}\``);
|
|
4949
|
+
const rest = names.length - shown.length;
|
|
4950
|
+
return shown.join(", ") + (rest > 0 ? `, and ${rest} more` : "");
|
|
4951
|
+
}
|
|
4952
|
+
function buildFilterWarning(filter, data, outcome) {
|
|
4953
|
+
const present = availableFields(data);
|
|
4954
|
+
const refs = referencedFields(filter);
|
|
4955
|
+
const unknown = refs.top.filter((name) => !present.includes(name) && !appearsBelowTopLevel(data, name));
|
|
4956
|
+
const unchecked = refs.nested.filter((name) => !present.includes(name));
|
|
4957
|
+
const count = countRecords(data);
|
|
4958
|
+
const scope = `${count} record(s)`;
|
|
4959
|
+
const isNotAField = (names) => `${nameList(names)} ${names.length === 1 ? "is not a field" : "are not fields"} on the ${scope} it read`;
|
|
4960
|
+
const soundness = unchecked.length > 0 ? `${nameList(unchecked)} ${unchecked.length === 1 ? "reads" : "read"} below the top level, so ${unchecked.length === 1 ? "it was" : "they were"} not checked against the record keys` : refs.top.length > 0 ? "every field it names exists" : "";
|
|
4961
|
+
const withSoundness = (base) => soundness ? `${base}, and ${soundness}` : base;
|
|
4962
|
+
let detail;
|
|
4963
|
+
let lede;
|
|
4964
|
+
if (outcome.kind === "null-elements") {
|
|
4965
|
+
lede = "returned only null values";
|
|
4966
|
+
const projected = `all ${outcome.count} of the elements it projected are null`;
|
|
4967
|
+
if (unknown.length > 0) {
|
|
4968
|
+
detail = `${projected}, because ${isNotAField(unknown)}`;
|
|
4969
|
+
} else if (unchecked.length > 0) {
|
|
4970
|
+
detail = withSoundness(projected);
|
|
4971
|
+
} else {
|
|
4972
|
+
detail = `${projected}, and every field it names exists — a multi-select list ` + `(\`[A, B]\`) reads each entry against \`Data\` itself, so on a list ` + `result use \`[].[A, B]\` or \`[].{a: A, b: B}\` to read the records`;
|
|
4973
|
+
}
|
|
4974
|
+
} else if (outcome.kind === "null-rows") {
|
|
4975
|
+
lede = "returned only null values";
|
|
4976
|
+
const projected = `every value it projected is null on all ${outcome.count} row(s)`;
|
|
4977
|
+
detail = unknown.length > 0 ? `${projected}, because ${isNotAField(unknown)}` : withSoundness(projected);
|
|
4978
|
+
} else if (outcome.kind === "null-values") {
|
|
4979
|
+
lede = "returned only null values";
|
|
4980
|
+
const projected = `every key it projected (${nameList(outcome.keys)}) is null`;
|
|
4981
|
+
const sameSet = unknown.length === outcome.keys.length && outcome.keys.every((k) => unknown.includes(k));
|
|
4982
|
+
if (sameSet) {
|
|
4983
|
+
detail = `${isNotAField(outcome.keys)}, so every key it projected is null`;
|
|
4984
|
+
} else if (unknown.length > 0) {
|
|
4985
|
+
detail = `${projected}, because ${isNotAField(unknown)}`;
|
|
4986
|
+
} else {
|
|
4987
|
+
detail = withSoundness(`${projected} on all ${scope}`);
|
|
4988
|
+
}
|
|
4989
|
+
} else {
|
|
4990
|
+
lede = "returned nothing";
|
|
4991
|
+
if (unknown.length > 0) {
|
|
4992
|
+
detail = isNotAField(unknown);
|
|
4993
|
+
} else {
|
|
4994
|
+
const selection = refs.hasPredicate ? "no record matched" : "the expression resolved to no value";
|
|
4995
|
+
detail = withSoundness(`${selection} — it read ${scope}`);
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4998
|
+
const fields = present.length > 0 ? ` Available fields: ${nameList(present)}.` : "";
|
|
4999
|
+
return `--output-filter ${JSON.stringify(filter)} ${lede}: ${detail}.${fields}`;
|
|
5000
|
+
}
|
|
5001
|
+
function appendWarning(existing, note) {
|
|
5002
|
+
return existing ? `${existing} ${note}` : note;
|
|
5003
|
+
}
|
|
5004
|
+
function applyFilter(data, filter, onWarning) {
|
|
4847
5005
|
const codec = requireJmespath();
|
|
4848
5006
|
let result;
|
|
4849
5007
|
try {
|
|
@@ -4851,9 +5009,34 @@ function applyFilter(data, filter) {
|
|
|
4851
5009
|
} catch (err) {
|
|
4852
5010
|
throw new FilterEvaluationError(filter, err);
|
|
4853
5011
|
}
|
|
4854
|
-
|
|
5012
|
+
const inputCount = countRecords(data);
|
|
5013
|
+
const warn = (outcome) => {
|
|
5014
|
+
if (inputCount === 0)
|
|
5015
|
+
return;
|
|
5016
|
+
const note = buildFilterWarning(filter, data, outcome);
|
|
5017
|
+
getOutputSink().writeErr(`[WARN] ${note}
|
|
5018
|
+
`);
|
|
5019
|
+
onWarning?.(note);
|
|
5020
|
+
};
|
|
5021
|
+
const warnIfAllNull = (records) => {
|
|
5022
|
+
const keys = alwaysNullKeys(records);
|
|
5023
|
+
if (keys.length > 0)
|
|
5024
|
+
warn({ kind: "null-values", keys });
|
|
5025
|
+
};
|
|
5026
|
+
if (result == null) {
|
|
5027
|
+
warn({ kind: "empty" });
|
|
4855
5028
|
return [];
|
|
5029
|
+
}
|
|
4856
5030
|
if (Array.isArray(result)) {
|
|
5031
|
+
if (result.length === 0) {
|
|
5032
|
+
warn({ kind: "empty" });
|
|
5033
|
+
} else if (result.every((entry) => entry === null)) {
|
|
5034
|
+
warn({ kind: "null-elements", count: result.length });
|
|
5035
|
+
} else if (result.every((entry) => Array.isArray(entry)) && carriesOnlyNulls(result)) {
|
|
5036
|
+
warn({ kind: "null-rows", count: result.length });
|
|
5037
|
+
} else {
|
|
5038
|
+
warnIfAllNull(result);
|
|
5039
|
+
}
|
|
4857
5040
|
if (result.length > 0 && typeof result[0] !== "object") {
|
|
4858
5041
|
const fieldMatch = filter.match(/\.(\w+)$/);
|
|
4859
5042
|
const key = fieldMatch ? fieldMatch[1] : "Value";
|
|
@@ -4861,10 +5044,30 @@ function applyFilter(data, filter) {
|
|
|
4861
5044
|
}
|
|
4862
5045
|
return result;
|
|
4863
5046
|
}
|
|
4864
|
-
if (typeof result === "object")
|
|
5047
|
+
if (typeof result === "object") {
|
|
5048
|
+
warnIfAllNull([result]);
|
|
4865
5049
|
return result;
|
|
5050
|
+
}
|
|
4866
5051
|
return { Value: result };
|
|
4867
5052
|
}
|
|
5053
|
+
function filterFailureData(envelope) {
|
|
5054
|
+
const filter = getOutputFilter();
|
|
5055
|
+
const data = envelope.Data;
|
|
5056
|
+
if (!filter || data == null)
|
|
5057
|
+
return;
|
|
5058
|
+
let note;
|
|
5059
|
+
const [filterError, filtered] = catchError(() => applyFilter(data, filter, (n) => {
|
|
5060
|
+
note = n;
|
|
5061
|
+
}));
|
|
5062
|
+
if (filterError) {
|
|
5063
|
+
envelope.Warning = appendWarning(envelope.Warning, `--output-filter was not applied, so Data is shown in full: ${filterError.message}`);
|
|
5064
|
+
return;
|
|
5065
|
+
}
|
|
5066
|
+
envelope.Data = filtered;
|
|
5067
|
+
if (note) {
|
|
5068
|
+
envelope.Warning = appendWarning(envelope.Warning, note);
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
4868
5071
|
function defaultErrorCodeForResult(result) {
|
|
4869
5072
|
switch (result) {
|
|
4870
5073
|
case RESULTS.AuthenticationError:
|
|
@@ -4947,8 +5150,14 @@ var OutputFormatter;
|
|
|
4947
5150
|
}
|
|
4948
5151
|
const filter = getOutputFilter();
|
|
4949
5152
|
if (filter) {
|
|
4950
|
-
|
|
5153
|
+
let note;
|
|
5154
|
+
const filtered = applyFilter(data.Data, filter, (n) => {
|
|
5155
|
+
note = n;
|
|
5156
|
+
});
|
|
4951
5157
|
data.Data = normalize ? normalizeDataKeys(filtered, verbatim, subtrees && { refs: subtrees.refs, mode: "skip" }) : filtered;
|
|
5158
|
+
if (note) {
|
|
5159
|
+
data.Warning = appendWarning(data.Warning, note);
|
|
5160
|
+
}
|
|
4952
5161
|
}
|
|
4953
5162
|
logOutput(normalizeOutputKeys(data), getOutputFormat(), options?.tableRowStyle);
|
|
4954
5163
|
}
|
|
@@ -4976,6 +5185,7 @@ var OutputFormatter;
|
|
|
4976
5185
|
delete envelope.TelemetryErrorClass;
|
|
4977
5186
|
delete envelope.TelemetryTerminalOutcome;
|
|
4978
5187
|
delete envelope.TelemetryTerminalSignal;
|
|
5188
|
+
filterFailureData(envelope);
|
|
4979
5189
|
if (!suppressTelemetry) {
|
|
4980
5190
|
telemetry.trackEvent(CommonTelemetryEvents.Error, {
|
|
4981
5191
|
result: data.Result,
|
|
@@ -5024,13 +5234,20 @@ var OutputFormatter;
|
|
|
5024
5234
|
OutputFormatter.log = log;
|
|
5025
5235
|
function formatToString(data) {
|
|
5026
5236
|
const filter = getOutputFilter();
|
|
5237
|
+
const envelope = data;
|
|
5027
5238
|
if ("Data" in data && data.Data != null) {
|
|
5028
5239
|
data.Data = normalizeDataKeys(data.Data);
|
|
5029
5240
|
if (filter) {
|
|
5030
|
-
|
|
5241
|
+
let note;
|
|
5242
|
+
data.Data = normalizeDataKeys(applyFilter(data.Data, filter, (n) => {
|
|
5243
|
+
note = n;
|
|
5244
|
+
}));
|
|
5245
|
+
if (note) {
|
|
5246
|
+
envelope.Warning = appendWarning(envelope.Warning, note);
|
|
5247
|
+
}
|
|
5031
5248
|
}
|
|
5032
5249
|
}
|
|
5033
|
-
const output = normalizeOutputKeys(
|
|
5250
|
+
const output = normalizeOutputKeys(envelope);
|
|
5034
5251
|
const lines = [];
|
|
5035
5252
|
const sink = getOutputSink();
|
|
5036
5253
|
printOutput(output, getOutputFormat(), (msg) => {
|
|
@@ -7159,4 +7376,4 @@ var CLI_EXTERNAL_USER_ID = "app-user-cli-cas";
|
|
|
7159
7376
|
|
|
7160
7377
|
export { package_default, getFileSystem, catchError, extractErrorDetails, extractErrorMessageSync, RESULTS, EXIT_CODES, OutputFormatter, InvalidArgumentError, Command, processContext, requireConfirmation, instructionsFor, canPrompt, parseBoundedInt, getSdkUserAgentToken, addSdkUserAgentHeader, getLoginStatusAsync, CLI_EXTERNAL_USER_ID };
|
|
7161
7378
|
|
|
7162
|
-
//# debugId=
|
|
7379
|
+
//# debugId=4D4638E7621A8A0964756E2164756E21
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
parseBoundedInt,
|
|
21
21
|
processContext,
|
|
22
22
|
requireConfirmation
|
|
23
|
-
} from "./tool-
|
|
23
|
+
} from "./tool-71f0nc7a.js";
|
|
24
24
|
import {
|
|
25
25
|
__require
|
|
26
26
|
} from "./tool-1yh8g550.js";
|
|
@@ -12481,7 +12481,7 @@ var resolveEntryPoint = (entryPoints, requested) => {
|
|
|
12481
12481
|
var resolveBinding = async (target) => {
|
|
12482
12482
|
const processKey = target.processKey.toLowerCase();
|
|
12483
12483
|
const folderKey = target.folderKey.toLowerCase();
|
|
12484
|
-
const { listVoiceEntryPoints } = await import("./cas-client-
|
|
12484
|
+
const { listVoiceEntryPoints } = await import("./cas-client-zx0f3ens.js");
|
|
12485
12485
|
const [epError, entryPoints] = await catchError(listVoiceEntryPoints(processKey, folderKey));
|
|
12486
12486
|
if (epError) {
|
|
12487
12487
|
await failFromError(epError);
|
|
@@ -12497,7 +12497,7 @@ var resolveBinding = async (target) => {
|
|
|
12497
12497
|
var registerTrunksCommand = (program) => {
|
|
12498
12498
|
const trunks = program.command("trunks").description("Inspect the SIP trunks (phone numbers) configured for conversational voice.");
|
|
12499
12499
|
trunks.command("list").description("List the tenant's SIP trunks. Voice flows dial out from an outbound-enabled trunk's phone number (`inputs.from` on Create outgoing call); inbound-enabled trunks route incoming calls to a published process. Trunks are connected in Instance Management under Phone numbers.").option("--direction <direction>", "Only trunks enabled for this call direction: inbound or outbound (default: all trunks)", parseDirection).trackedAction(processContext, async (options) => {
|
|
12500
|
-
const { listSipTrunks } = await import("./cas-client-
|
|
12500
|
+
const { listSipTrunks } = await import("./cas-client-zx0f3ens.js");
|
|
12501
12501
|
const [listError, trunks2] = await catchError(listSipTrunks(options.direction));
|
|
12502
12502
|
if (listError) {
|
|
12503
12503
|
await failFromError(listError);
|
|
@@ -12513,7 +12513,7 @@ var registerTrunksCommand = (program) => {
|
|
|
12513
12513
|
const plan = planAssign(options);
|
|
12514
12514
|
if (plan === null)
|
|
12515
12515
|
return;
|
|
12516
|
-
const { resolveSipTrunk, updateSipTrunkBinding } = await import("./cas-client-
|
|
12516
|
+
const { resolveSipTrunk, updateSipTrunkBinding } = await import("./cas-client-zx0f3ens.js");
|
|
12517
12517
|
const [trunkError, trunk] = await catchError(resolveSipTrunk(trunkArg));
|
|
12518
12518
|
if (trunkError) {
|
|
12519
12519
|
await failFromError(trunkError);
|
package/dist/tool.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
metadata,
|
|
3
3
|
registerCommands
|
|
4
|
-
} from "./tool-
|
|
4
|
+
} from "./tool-p92sytgg.js";
|
|
5
5
|
import"./tool-nkyhbf79.js";
|
|
6
|
-
import"./tool-
|
|
6
|
+
import"./tool-71f0nc7a.js";
|
|
7
7
|
import"./tool-mpvp5rqs.js";
|
|
8
8
|
import"./tool-1yh8g550.js";
|
|
9
9
|
export {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/conversational-tool",
|
|
3
3
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
4
|
-
"version": "1.202.1-preview.
|
|
4
|
+
"version": "1.202.1-preview.238",
|
|
5
5
|
"description": "Handle conversations with deployed UiPath conversational processes.",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -17,5 +17,5 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"dist"
|
|
19
19
|
],
|
|
20
|
-
"gitHead": "
|
|
20
|
+
"gitHead": "537e43df1d3583633cfd3a0b88634f9ce3ba9d6b"
|
|
21
21
|
}
|