@social-mail/social-mail-web-server 1.7.6 → 1.7.7
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/common/spawnPromise.js +3 -3
- package/dist/common/spawnPromise.js.map +1 -1
- package/dist/isolated/FileInfo.d.ts +9 -0
- package/dist/isolated/FileInfo.d.ts.map +1 -0
- package/dist/isolated/FileInfo.js +11 -0
- package/dist/isolated/FileInfo.js.map +1 -0
- package/dist/isolated/IsolatedProcess.d.ts +11 -0
- package/dist/isolated/IsolatedProcess.d.ts.map +1 -0
- package/dist/isolated/IsolatedProcess.js +26 -0
- package/dist/isolated/IsolatedProcess.js.map +1 -0
- package/dist/isolated/extract/IExtract.d.ts +9 -0
- package/dist/isolated/extract/IExtract.d.ts.map +1 -0
- package/dist/isolated/extract/IExtract.js +2 -0
- package/dist/isolated/extract/IExtract.js.map +1 -0
- package/dist/isolated/extract/index.d.ts +7 -0
- package/dist/isolated/extract/index.d.ts.map +1 -0
- package/dist/isolated/extract/index.js +51 -0
- package/dist/isolated/extract/index.js.map +1 -0
- package/dist/isolated/image-converter/IImageConverter.d.ts +11 -0
- package/dist/isolated/image-converter/IImageConverter.d.ts.map +1 -0
- package/dist/isolated/image-converter/IImageConverter.js +2 -0
- package/dist/isolated/image-converter/IImageConverter.js.map +1 -0
- package/dist/isolated/image-converter/formats/size.d.ts +5 -0
- package/dist/isolated/image-converter/formats/size.d.ts.map +1 -0
- package/dist/{server/services → isolated}/image-converter/formats/size.js +1 -0
- package/dist/isolated/image-converter/formats/size.js.map +1 -0
- package/dist/isolated/image-converter/index.d.ts +2 -0
- package/dist/isolated/image-converter/index.d.ts.map +1 -0
- package/dist/isolated/image-converter/index.js +59 -0
- package/dist/isolated/image-converter/index.js.map +1 -0
- package/dist/server/services/convert/FileConversionService.d.ts.map +1 -1
- package/dist/server/services/convert/FileConversionService.js +1 -1
- package/dist/server/services/convert/FileConversionService.js.map +1 -1
- package/dist/server/services/extract/Extract.d.ts.map +1 -1
- package/dist/server/services/extract/Extract.js +3 -53
- package/dist/server/services/extract/Extract.js.map +1 -1
- package/dist/server/services/image-converter/ImageConverterService.d.ts.map +1 -1
- package/dist/server/services/image-converter/ImageConverterService.js +7 -22
- package/dist/server/services/image-converter/ImageConverterService.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/common/spawnPromise.ts +3 -3
- package/src/isolated/FileInfo.ts +13 -0
- package/src/isolated/IsolatedProcess.ts +33 -0
- package/src/isolated/extract/IExtract.ts +4 -0
- package/src/isolated/extract/index.ts +59 -0
- package/src/isolated/image-converter/IImageConverter.ts +6 -0
- package/src/{server/services → isolated}/image-converter/formats/size.ts +1 -2
- package/src/isolated/image-converter/index.ts +73 -0
- package/src/server/services/convert/FileConversionService.ts +1 -1
- package/src/server/services/extract/Extract.ts +3 -48
- package/src/server/services/image-converter/ImageConverterService.ts +10 -25
- package/dist/server/services/image-converter/formats/size.d.ts +0 -4
- package/dist/server/services/image-converter/formats/size.d.ts.map +0 -1
- package/dist/server/services/image-converter/formats/size.js.map +0 -1
package/package.json
CHANGED
|
@@ -31,10 +31,10 @@ export const spawnPromise = (path, args?: string[], options?: SpawnOptionsWithou
|
|
|
31
31
|
});
|
|
32
32
|
cd.on("close", (status) => {
|
|
33
33
|
if (status>0) {
|
|
34
|
+
if (logError) {
|
|
35
|
+
console.error(all);
|
|
36
|
+
}
|
|
34
37
|
if (throwOnFail) {
|
|
35
|
-
if (logError) {
|
|
36
|
-
console.error(all);
|
|
37
|
-
}
|
|
38
38
|
reject(new Error(all.join("\n")));
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { LocalFile } from "@entity-access/server-pages/dist/core/LocalFile.js";
|
|
2
|
+
|
|
3
|
+
export default class FileInfo {
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
public static from(file: LocalFile) {
|
|
7
|
+
return new FileInfo(file.path, file.contentType, file.fileName);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
constructor(public readonly path: string, public readonly contentType: string, public readonly fileName: string) {
|
|
11
|
+
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { fileURLToPath } from "url";
|
|
2
|
+
import { spawnPromise } from "../common/spawnPromise.js";
|
|
3
|
+
import FileInfo from "./FileInfo.js";
|
|
4
|
+
import IImageConverter from "./image-converter/IImageConverter.js";
|
|
5
|
+
import IExtract from "./extract/IExtract.js";
|
|
6
|
+
|
|
7
|
+
export default class IsolatedProcess {
|
|
8
|
+
|
|
9
|
+
static async convertImage(p: IImageConverter) {
|
|
10
|
+
return spawnPromise(process.execPath, [
|
|
11
|
+
"--enable-source-maps",
|
|
12
|
+
fileURLToPath(import.meta.resolve("./image-converter/index.js")),
|
|
13
|
+
JSON.stringify(p),
|
|
14
|
+
], {
|
|
15
|
+
logCommand: false,
|
|
16
|
+
logError: true
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static async extractText(p: IExtract) {
|
|
21
|
+
const result = await spawnPromise(process.execPath, [
|
|
22
|
+
"--enable-source-maps",
|
|
23
|
+
fileURLToPath(import.meta.resolve("./extract/index.js")),
|
|
24
|
+
JSON.stringify(p),
|
|
25
|
+
], {
|
|
26
|
+
logCommand: false,
|
|
27
|
+
logError: true
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return result.all;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import tesseract from "tesseract.js";
|
|
4
|
+
import FileInfo from "../FileInfo.js";
|
|
5
|
+
import IExtract from "./IExtract.js";
|
|
6
|
+
const { recognize } = tesseract;
|
|
7
|
+
|
|
8
|
+
export class ExtractWorker {
|
|
9
|
+
static async text({ input: file, useOcr }: IExtract) {
|
|
10
|
+
try {
|
|
11
|
+
if (/^image\//.test(file.contentType)) {
|
|
12
|
+
if (useOcr) {
|
|
13
|
+
return this.ocr(file);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return await readFile(file.path, "utf8");
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error(error);
|
|
20
|
+
}
|
|
21
|
+
return "";
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static async ocr(file: FileInfo) {
|
|
26
|
+
try {
|
|
27
|
+
const buffer = await readFile(file.path);
|
|
28
|
+
const result = await recognize(buffer);
|
|
29
|
+
return result.data.text;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error(error);
|
|
32
|
+
}
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
const [execPath, self, p] = process.argv;
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
ExtractWorker.text(JSON.parse(p))
|
|
42
|
+
.then((r) => console.log(r))
|
|
43
|
+
.catch(console.error);
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
// parentPort.on("message", async ({ file, useOcr }) => {
|
|
47
|
+
// try {
|
|
48
|
+
// const result = await ExtractWorker.text(file, useOcr);
|
|
49
|
+
// parentPort.postMessage({ result });
|
|
50
|
+
// process.exit(0);
|
|
51
|
+
// } catch (error) {
|
|
52
|
+
// try {
|
|
53
|
+
// parentPort.postMessage({ error: error.stack ?? error.toString() });
|
|
54
|
+
// } catch (er1) {
|
|
55
|
+
// // we will ignore this.. we need to close process successfully.
|
|
56
|
+
// }
|
|
57
|
+
// process.exit(0);
|
|
58
|
+
// }
|
|
59
|
+
// });
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/* eslint-disable no-bitwise */
|
|
2
|
-
import { LocalFile } from "@entity-access/server-pages/dist/core/LocalFile.js";
|
|
3
2
|
import sharp from "sharp";
|
|
4
3
|
|
|
5
|
-
export default async function (file:
|
|
4
|
+
export default async function (file: { path: string }, ... args: any[] ): Promise<sharp.Sharp> {
|
|
6
5
|
|
|
7
6
|
const size = {
|
|
8
7
|
failOnError: false,
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import sharp from "sharp";
|
|
2
|
+
import { parse } from "path";
|
|
3
|
+
import IImageConverter from "./IImageConverter.js";
|
|
4
|
+
|
|
5
|
+
async function transform({
|
|
6
|
+
type,
|
|
7
|
+
input,
|
|
8
|
+
output,
|
|
9
|
+
outputFileName = "file.jpg"
|
|
10
|
+
}: IImageConverter
|
|
11
|
+
) {
|
|
12
|
+
|
|
13
|
+
let { ext } = parse(outputFileName);
|
|
14
|
+
let fileName = type;
|
|
15
|
+
const matches = type.match(/(\w+)\(([^\)]+)\)/);
|
|
16
|
+
let args = [];
|
|
17
|
+
if (matches) {
|
|
18
|
+
fileName = matches[1];
|
|
19
|
+
args = matches[2].split(',').map( (x) => parseFloat(x) );
|
|
20
|
+
switch(fileName) {
|
|
21
|
+
case "jpg":
|
|
22
|
+
ext = ".jpg";
|
|
23
|
+
fileName = "size";
|
|
24
|
+
break;
|
|
25
|
+
case "png":
|
|
26
|
+
ext = ".png";
|
|
27
|
+
fileName = "size";
|
|
28
|
+
break;
|
|
29
|
+
case "gif":
|
|
30
|
+
ext = ".gif";
|
|
31
|
+
fileName = "size";
|
|
32
|
+
break;
|
|
33
|
+
case "webp":
|
|
34
|
+
ext = ".webp";
|
|
35
|
+
fileName = "size";
|
|
36
|
+
break;
|
|
37
|
+
case "ico":
|
|
38
|
+
ext = ".ico";
|
|
39
|
+
fileName = "size";
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { default: fx } = await import("./formats/" + fileName + ".js");
|
|
45
|
+
let r: sharp.Sharp = await fx(input, ... args);
|
|
46
|
+
|
|
47
|
+
switch(ext) {
|
|
48
|
+
case ".jpg":
|
|
49
|
+
r = r.jpeg({ quality: 95});
|
|
50
|
+
break;
|
|
51
|
+
case ".png":
|
|
52
|
+
r = r.png({
|
|
53
|
+
compressionLevel: 9
|
|
54
|
+
});
|
|
55
|
+
break;
|
|
56
|
+
case ".webp":
|
|
57
|
+
r = r.webp({
|
|
58
|
+
nearLossless: true,
|
|
59
|
+
quality: 95
|
|
60
|
+
});
|
|
61
|
+
break;
|
|
62
|
+
case ".gif":
|
|
63
|
+
r = r.gif();
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await r.toFile(output.path);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const [execPath, self, p] = process.argv;
|
|
71
|
+
|
|
72
|
+
transform(JSON.parse(p))
|
|
73
|
+
.catch(console.error);
|
|
@@ -2,7 +2,6 @@ import Inject, { RegisterScoped, ServiceProvider } from "@entity-access/entity-a
|
|
|
2
2
|
import TempFileService from "../../storage/TempFileService.js";
|
|
3
3
|
import { LocalFile } from "@entity-access/server-pages/dist/core/LocalFile.js";
|
|
4
4
|
import { Convert } from "./Convert.js";
|
|
5
|
-
import PdfDoc from "../extract/pdf/PdfDoc.js";
|
|
6
5
|
import FFCommand from "./FFCommand.js";
|
|
7
6
|
import AppFile from "../../model/entities/AppFile.js";
|
|
8
7
|
import TimeSpan from "@entity-access/entity-access/dist/types/TimeSpan.js";
|
|
@@ -13,6 +12,7 @@ import Page from "@entity-access/server-pages/dist/Page.js";
|
|
|
13
12
|
import StringFormat from "../../../common/StringFormat.js";
|
|
14
13
|
import RateLimiterService from "../rate-limiter/RateLimiterService.js";
|
|
15
14
|
import { globalEnv } from "../../../common/globalEnv.js";
|
|
15
|
+
import PdfDoc from "../extract/pdf/PdfDoc.js";
|
|
16
16
|
|
|
17
17
|
interface IDownloadConvertedFile {
|
|
18
18
|
appFile: AppFile;
|
|
@@ -6,10 +6,11 @@ import FileInfo from "./FileInfo.js";
|
|
|
6
6
|
import { LocalFile } from "@entity-access/server-pages/dist/core/LocalFile.js";
|
|
7
7
|
import TempFolder from "@entity-access/server-pages/dist/core/TempFolder.js";
|
|
8
8
|
import { FileType } from "../../../common/FileType.js";
|
|
9
|
-
import PdfDoc from "./pdf/PdfDoc.js";
|
|
10
9
|
import { readFile } from "fs/promises";
|
|
11
10
|
import { Convert } from "../convert/Convert.js";
|
|
12
11
|
import LockFile from "../../storage/LockFile.js";
|
|
12
|
+
import PdfDoc from "./pdf/PdfDoc.js";
|
|
13
|
+
import IsolatedProcess from "../../../isolated/IsolatedProcess.js";
|
|
13
14
|
|
|
14
15
|
export class Extract {
|
|
15
16
|
static async text(src: LocalFile, useOcr = false) {
|
|
@@ -44,52 +45,6 @@ export class Extract {
|
|
|
44
45
|
|
|
45
46
|
using lock = await LockFile.lock("extract-worker-lock");
|
|
46
47
|
|
|
47
|
-
return await
|
|
48
|
-
let resolved = false;
|
|
49
|
-
console.log(`Extracting text from ${file.path}`);
|
|
50
|
-
const execArgv = ["--enable-source-maps"];
|
|
51
|
-
if (process.argv.includes("--trace-deprecation")) {
|
|
52
|
-
execArgv.push("--trace-deprecation");
|
|
53
|
-
}
|
|
54
|
-
const w = new Worker( dirname(fileURLToPath(import.meta.url)) + "/ExtractWorker.js",{
|
|
55
|
-
execArgv,
|
|
56
|
-
resourceLimits: {
|
|
57
|
-
maxOldGenerationSizeMb: 1024
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
w.on("message", ({ result, error }) => {
|
|
61
|
-
if (resolved) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
if (result) {
|
|
65
|
-
resolved = true;
|
|
66
|
-
resolve(result);
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
if (error) {
|
|
70
|
-
reject(error);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
w.on("error", (error) => {
|
|
74
|
-
if(resolved) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
console.error(error);
|
|
78
|
-
resolved = true;
|
|
79
|
-
// reject(error);
|
|
80
|
-
console.error(`Failed to extract text for ${src.fileName} ${error.stack ?? error}`);
|
|
81
|
-
resolve("");
|
|
82
|
-
});
|
|
83
|
-
w.on("exit", (n) => {
|
|
84
|
-
if (n>0) {
|
|
85
|
-
if (resolved) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
resolved = true;
|
|
89
|
-
reject(new Error(`Worker exited with ${n}`));
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
w.postMessage({ file: { ... FileInfo.from(file)}, useOcr });
|
|
93
|
-
});
|
|
48
|
+
return await IsolatedProcess.extractText({ input: { fileName: file.fileName, contentType: file.contentType, path: file.path }, useOcr });
|
|
94
49
|
}
|
|
95
50
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import Inject, { RegisterScoped } from "@entity-access/entity-access/dist/di/di.js";
|
|
2
2
|
import TempFileService from "../../storage/TempFileService.js";
|
|
3
|
-
import sharp from "sharp";
|
|
4
3
|
import { join, parse } from "path";
|
|
5
4
|
import { LocalFile } from "@entity-access/server-pages/dist/core/LocalFile.js";
|
|
6
5
|
import { Convert } from "../convert/Convert.js";
|
|
@@ -9,6 +8,7 @@ import { spawnPromise } from "../../../common/spawnPromise.js";
|
|
|
9
8
|
import { copyFile, readdir } from "fs/promises";
|
|
10
9
|
import FFCommand from "../convert/FFCommand.js";
|
|
11
10
|
import { ImagesFolder } from "../../../common/ImagesFolder.js";
|
|
11
|
+
import IsolatedProcess from "../../../isolated/IsolatedProcess.js";
|
|
12
12
|
|
|
13
13
|
@RegisterScoped
|
|
14
14
|
export default class ImageConverterService {
|
|
@@ -92,6 +92,7 @@ export default class ImageConverterService {
|
|
|
92
92
|
async transform(type, file: LocalFile, outputFileName = "file.jpg") {
|
|
93
93
|
|
|
94
94
|
let { ext } = parse(outputFileName);
|
|
95
|
+
|
|
95
96
|
let fileName = type;
|
|
96
97
|
const matches = type.match(/(\w+)\(([^\)]+)\)/);
|
|
97
98
|
let args = [];
|
|
@@ -127,31 +128,15 @@ export default class ImageConverterService {
|
|
|
127
128
|
file = ImagesFolder.localFile("png", "unavailable.png");
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
const { default: fx } = await import("./formats/" + fileName + ".js");
|
|
131
|
-
let r: sharp.Sharp = await fx(file, ... args);
|
|
132
|
-
|
|
133
|
-
switch(ext) {
|
|
134
|
-
case ".jpg":
|
|
135
|
-
r = r.jpeg({ quality: 95});
|
|
136
|
-
break;
|
|
137
|
-
case ".png":
|
|
138
|
-
r = r.png({
|
|
139
|
-
compressionLevel: 9
|
|
140
|
-
});
|
|
141
|
-
break;
|
|
142
|
-
case ".webp":
|
|
143
|
-
r = r.webp({
|
|
144
|
-
nearLossless: true,
|
|
145
|
-
quality: 95
|
|
146
|
-
});
|
|
147
|
-
break;
|
|
148
|
-
case ".gif":
|
|
149
|
-
r = r.gif();
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
131
|
const tf = await this.tempFileService.createTempFile(ext, outputFileName);
|
|
154
|
-
|
|
132
|
+
|
|
133
|
+
await IsolatedProcess.convertImage({
|
|
134
|
+
type,
|
|
135
|
+
input: { path: file.path },
|
|
136
|
+
output: { path: tf.path },
|
|
137
|
+
outputFileName
|
|
138
|
+
});
|
|
139
|
+
|
|
155
140
|
return tf;
|
|
156
141
|
}
|
|
157
142
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"size.d.ts","sourceRoot":"","sources":["../../../../../src/server/services/image-converter/formats/size.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,oDAAoD,CAAC;AAC/E,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,yBAA+B,IAAI,EAAE,SAAS,EAAE,GAAI,IAAI,EAAE,GAAG,EAAE,GAAI,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAmCtF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"size.js","sourceRoot":"","sources":["../../../../../src/server/services/image-converter/formats/size.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAC,OAAO,CAAC,KAAK,WAAW,IAAe,EAAE,GAAI,IAAW;IAE3D,MAAM,IAAI,GAAG;QACT,WAAW,EAAE,KAAK;KACd,CAAC;IAET,IAAI,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC;QAExB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpB,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;YACf,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QACpB,CAAC;aAAM,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,uCAAuC;YACvC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5B,CAAC;aAAM,CAAC;YACJ,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;YAC7B,uCAAuC;YACvC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;YACrB,uCAAuC;YACvC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;YACvB,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC;QACvB,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC;YACxC,OAAO,MAAO,KAAK,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,EAAC,CAAS,CAAC;QACrE,CAAC;QACD,OAAO,MAAO,KAAK,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,EAAC,CAAS;aAC/D,MAAM,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;QAC1B,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;AACL,CAAC"}
|