@iola_adm/iola-cli 0.2.32 → 0.2.33
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/package.json +1 -1
- package/src/cli.js +50 -10
- package/test/smoke-test.js +1 -1
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -4148,8 +4148,13 @@ async function yandexMailCount(options = {}) {
|
|
|
4148
4148
|
async function yandexMailSearch(query, options = {}) {
|
|
4149
4149
|
const normalized = normalizeGeoText(query);
|
|
4150
4150
|
if (!normalized) return yandexMailList(options);
|
|
4151
|
+
const tokens = normalized.split(/\s+/u).filter((token) => token.length > 2 && !/^(про|обо?|где|что|кто|как)$/iu.test(token));
|
|
4151
4152
|
const rows = await yandexMailList({ ...options, limit: Math.max(50, Number(options.limit || 20) * 3) });
|
|
4152
|
-
return rows.filter((row) =>
|
|
4153
|
+
return rows.filter((row) => {
|
|
4154
|
+
const haystack = normalizeGeoText(`${row.from} ${row.subject} ${row.snippet}`);
|
|
4155
|
+
if (haystack.includes(normalized)) return true;
|
|
4156
|
+
return tokens.length > 0 && tokens.every((token) => haystack.includes(token));
|
|
4157
|
+
}).slice(0, Number(options.limit || 20));
|
|
4153
4158
|
}
|
|
4154
4159
|
|
|
4155
4160
|
async function yandexMailWatchTick(options = {}) {
|
|
@@ -4205,7 +4210,7 @@ async function yandexMailRead(uid, options = {}) {
|
|
|
4205
4210
|
try {
|
|
4206
4211
|
await imapAuthenticate(session, email, token);
|
|
4207
4212
|
await imapCommand(session, `SELECT ${quoteImapMailbox(options.mailbox || "INBOX")}`);
|
|
4208
|
-
const bodyAccessor = options.markSeen === false ? "BODY.PEEK[
|
|
4213
|
+
const bodyAccessor = options.markSeen === false ? "BODY.PEEK[]" : "BODY[]";
|
|
4209
4214
|
const fetch = await imapCommand(session, `UID FETCH ${Number(uid)} (UID FLAGS RFC822.SIZE BODY.PEEK[HEADER.FIELDS (DATE FROM SUBJECT MESSAGE-ID REFERENCES)] ${bodyAccessor})`, { timeout: 60000 });
|
|
4210
4215
|
return parseImapFetchSummaries(fetch, { full: true })[0] || { uid, status: "not-found" };
|
|
4211
4216
|
} finally {
|
|
@@ -4646,14 +4651,14 @@ function stripMailBody(value) {
|
|
|
4646
4651
|
const raw = String(value || "").replace(/\r/g, "");
|
|
4647
4652
|
const withoutFetch = raw
|
|
4648
4653
|
.replace(/^\* \d+ FETCH[^\n]*\n?/u, "")
|
|
4649
|
-
.replace(/^BODY
|
|
4654
|
+
.replace(/^BODY(?:\.PEEK)?\[[^\n]*\]\s*\{\d+\}\n?/imu, "")
|
|
4650
4655
|
.replace(/\n\)\s*$/u, "");
|
|
4651
|
-
const bodyMatch = withoutFetch.match(/BODY
|
|
4656
|
+
const bodyMatch = withoutFetch.match(/BODY(?:\.PEEK)?\[[^\n]*\]\s*\{\d+\}\s*([\s\S]*)/iu);
|
|
4652
4657
|
if (bodyMatch?.[1]) return extractMimeText(bodyMatch[1]);
|
|
4653
4658
|
const headerEnd = withoutFetch.indexOf("\n\n");
|
|
4654
4659
|
if (headerEnd < 0) return withoutFetch.replace(/[^\S\n]+/g, " ").trim();
|
|
4655
4660
|
const headers = withoutFetch.slice(0, headerEnd);
|
|
4656
|
-
const body = withoutFetch.slice(headerEnd + 2).replace(/^BODY
|
|
4661
|
+
const body = withoutFetch.slice(headerEnd + 2).replace(/^BODY(?:\.PEEK)?\[[^\n]*\]\s*\{\d+\}\n?/imu, "");
|
|
4657
4662
|
return extractMimeText(`${headers}\n\n${body}`);
|
|
4658
4663
|
}
|
|
4659
4664
|
|
|
@@ -4690,17 +4695,52 @@ function decodeMailPart(part) {
|
|
|
4690
4695
|
const encoding = headers.match(/^Content-Transfer-Encoding:\s*([^\n]+)/imu)?.[1]?.trim().toLocaleLowerCase("en-US") || "";
|
|
4691
4696
|
let decoded = body;
|
|
4692
4697
|
if (encoding === "base64") {
|
|
4693
|
-
|
|
4698
|
+
const clean = body.replace(/\s+/g, "");
|
|
4699
|
+
decoded = /^[A-Z0-9+/]+={0,2}$/iu.test(clean) && clean.length >= 8
|
|
4700
|
+
? Buffer.from(clean, "base64").toString("utf8")
|
|
4701
|
+
: body;
|
|
4694
4702
|
} else if (encoding === "quoted-printable") {
|
|
4695
4703
|
decoded = decodeQuotedPrintable(body);
|
|
4704
|
+
} else {
|
|
4705
|
+
const clean = body.replace(/\s+/g, "");
|
|
4706
|
+
if (/^[A-Z0-9+/]+={0,2}$/iu.test(clean) && clean.length >= 80) {
|
|
4707
|
+
const candidate = Buffer.from(clean, "base64").toString("utf8");
|
|
4708
|
+
if (/(<!doctype|<html|<body|[А-Яа-яЁё]{3,})/u.test(candidate)) decoded = candidate;
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
decoded = decodeEmbeddedBase64MailBody(decoded);
|
|
4712
|
+
const cleaned = decoded
|
|
4713
|
+
.replace(/<style\b[\s\S]*?<\/style>/giu, " ")
|
|
4714
|
+
.replace(/<script\b[\s\S]*?<\/script>/giu, " ")
|
|
4715
|
+
.replace(/<[^>]+>/g, " ")
|
|
4716
|
+
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]+/gu, " ")
|
|
4717
|
+
.replace(/[^\S\n]+/g, " ")
|
|
4718
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
4719
|
+
.trim();
|
|
4720
|
+
const firstCyrillic = cleaned.search(/[А-Яа-яЁё]{3,}/u);
|
|
4721
|
+
if (firstCyrillic > 0 && firstCyrillic < 120 && cleaned.slice(0, firstCyrillic).includes("�")) {
|
|
4722
|
+
return cleaned.slice(firstCyrillic).trim();
|
|
4696
4723
|
}
|
|
4697
|
-
return
|
|
4724
|
+
return cleaned;
|
|
4698
4725
|
}
|
|
4699
4726
|
|
|
4700
4727
|
function decodeQuotedPrintable(value) {
|
|
4701
4728
|
return Buffer.from(String(value || "").replace(/=\n/gu, "").replace(/=([A-F0-9]{2})/giu, (_, hex) => String.fromCharCode(parseInt(hex, 16))), "binary").toString("utf8");
|
|
4702
4729
|
}
|
|
4703
4730
|
|
|
4731
|
+
function decodeEmbeddedBase64MailBody(value) {
|
|
4732
|
+
const text = String(value || "");
|
|
4733
|
+
if (/(<!doctype|<html|<body|[А-Яа-яЁё]{3,})/u.test(text) && !/[A-Z0-9+/]{120,}/u.test(text)) return text;
|
|
4734
|
+
const matches = text.match(/[A-Z0-9+/=\s]{160,}/giu) || [];
|
|
4735
|
+
for (const match of matches) {
|
|
4736
|
+
const clean = match.replace(/\s+/g, "");
|
|
4737
|
+
if (!/^[A-Z0-9+/]+={0,2}$/iu.test(clean) || clean.length < 160) continue;
|
|
4738
|
+
const candidate = Buffer.from(clean, "base64").toString("utf8");
|
|
4739
|
+
if (/(<!doctype|<html|<body|[А-Яа-яЁё]{3,})/u.test(candidate)) return candidate;
|
|
4740
|
+
}
|
|
4741
|
+
return text;
|
|
4742
|
+
}
|
|
4743
|
+
|
|
4704
4744
|
async function yandexDavRequest(url, token, options = {}) {
|
|
4705
4745
|
const response = await fetch(url, {
|
|
4706
4746
|
method: options.method || "GET",
|
|
@@ -10236,7 +10276,7 @@ async function buildYandexDirectAnswer(question, history = []) {
|
|
|
10236
10276
|
if (!rows.length) return "В письме не нашел совпадений со слоями школ и детских садов.";
|
|
10237
10277
|
return ["Нашел в городских слоях:", ...rows.map((row, index) => `${index + 1}. ${row.name}${row.inn ? `, ИНН ${row.inn}` : ""}${row.address ? `, ${row.address}` : ""}`)].join("\n");
|
|
10238
10278
|
}
|
|
10239
|
-
if (/(
|
|
10279
|
+
if (/(адрес|карт|место|где)/iu.test(normalized) && /(письм|письма|письме)/iu.test(normalized)) {
|
|
10240
10280
|
const uid = resolveYandexMailUidFromQuestion(question, previousAssistantText);
|
|
10241
10281
|
if (!uid) return "Из какого письма взять адрес? Укажите номер из списка или UID.";
|
|
10242
10282
|
const rows = await yandexMailMapAddresses(uid, { mailbox: extractYandexMailboxName(question) || "INBOX" });
|
|
@@ -10366,7 +10406,7 @@ function isYandexServiceQuestion(normalized) {
|
|
|
10366
10406
|
|
|
10367
10407
|
function isYandexIdentityQuestion(normalized) {
|
|
10368
10408
|
const text = String(normalized || "");
|
|
10369
|
-
return /(
|
|
10409
|
+
return /(аккаунт|аккант|акант|акаунт|акк?аунт|профил|логин|кто подключен|какой.*подключ|email|e-mail)/iu.test(text)
|
|
10370
10410
|
&& /(яндекс|яндес|язндекс|язндекс|яндкс|yandex)/iu.test(text);
|
|
10371
10411
|
}
|
|
10372
10412
|
|
|
@@ -10378,7 +10418,7 @@ function isYandexMailFollowupQuestion(normalized, question) {
|
|
|
10378
10418
|
}
|
|
10379
10419
|
|
|
10380
10420
|
function cleanupYandexQuery(question) {
|
|
10381
|
-
const stop = /^(
|
|
10421
|
+
const stop = /^(?:в|на|у|из|для|по|про|о|об|обо|яндекс|yandex|найди|поиск|покажи|посмотри|проверь|почт\p{L}*|письм\p{L}*|календар\p{L}*|контакт\p{L}*)$/iu;
|
|
10382
10422
|
return String(question || "")
|
|
10383
10423
|
.replace(/[?.!]+$/u, "")
|
|
10384
10424
|
.split(/[^\p{L}\p{N}@._+-]+/gu)
|
package/test/smoke-test.js
CHANGED
|
@@ -81,7 +81,7 @@ assertIncludes(cliSource, "язндекс", "Yandex direct router should tolerat
|
|
|
81
81
|
assertIncludes(cliSource, "yandexMailCount", "Yandex mail should answer unread count questions directly");
|
|
82
82
|
assertIncludes(cliSource, "resolveYandexMailUidFromQuestion", "Yandex mail follow-ups should resolve selected message UID");
|
|
83
83
|
assertIncludes(cliSource, "extractYandexMailUidByOrdinal", "Yandex mail follow-ups should support numbered selections");
|
|
84
|
-
assertIncludes(cliSource, "BODY[
|
|
84
|
+
assertIncludes(cliSource, "BODY[]", "Yandex mail read should fetch full MIME messages and mark opened messages as seen");
|
|
85
85
|
assertIncludes(cliSource, "markSeen === false", "Yandex mail read should keep an explicit no-mark fallback");
|
|
86
86
|
assertIncludes(cliSource, "yandex_mail_folders", "Yandex mail should expose folders as a tool");
|
|
87
87
|
assertIncludes(cliSource, "yandex_mail_reply", "Yandex mail should expose reply as a tool");
|