@openclaw/line 2026.8.1-beta.3 → 2026.8.2

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.
Files changed (156) hide show
  1. package/dist/account-helpers-BSUyZwLm.js +12 -0
  2. package/dist/{accounts-BqEVsDoB.js → accounts-BBEFMYbY.js} +2 -1
  3. package/dist/api.js +2 -2
  4. package/dist/card-command-DS6A5V6o.js +252 -0
  5. package/dist/{channel-DmoKZXmG.js → channel-CLCy4jAa.js} +97 -357
  6. package/dist/channel-plugin-api.js +1 -1
  7. package/dist/channel.runtime-Ih7sq1GN.js +4 -0
  8. package/dist/contract-api.js +1 -1
  9. package/dist/doctor-contract-api.js +141 -0
  10. package/dist/index.js +5 -17
  11. package/dist/{markdown-to-line-rEaras_0.js → markdown-to-line--jytmFan.js} +36 -29
  12. package/dist/{monitor-BYXS_B6r.js → monitor-CSpwQCBI.js} +292 -194
  13. package/dist/monitor.runtime-CV8EjkIT.js +2 -0
  14. package/dist/outbound.runtime-DLqcz_BQ.js +3 -0
  15. package/dist/probe.runtime-DTxBWvKz.js +2 -0
  16. package/dist/rich-messages-CIP7xn5E.js +761 -0
  17. package/dist/runtime-api.js +12 -13
  18. package/dist/{send-receipt-DKZzgHMO.js → send-retry-CUvrarfE.js} +137 -413
  19. package/dist/{send-BJCeiiOB.js → send-wVUV00WS.js} +150 -109
  20. package/dist/setup-api.js +1 -1
  21. package/dist/{setup-surface-W0Dbgu58.js → setup-surface-CDlgkTKi.js} +47 -39
  22. package/dist/webhook-spool-contract-1rY8OJw8.js +59 -0
  23. package/node_modules/typebox/build/format/date.d.mts +1 -1
  24. package/node_modules/typebox/build/format/date.mjs +1 -1
  25. package/node_modules/typebox/build/format/date_time.d.mts +2 -2
  26. package/node_modules/typebox/build/format/date_time.mjs +3 -3
  27. package/node_modules/typebox/build/format/email.d.mts +1 -1
  28. package/node_modules/typebox/build/format/email.mjs +2 -2
  29. package/node_modules/typebox/build/format/hostname.mjs +2 -10
  30. package/node_modules/typebox/build/format/idn_email.d.mts +1 -1
  31. package/node_modules/typebox/build/format/idn_email.mjs +3 -3
  32. package/node_modules/typebox/build/format/idn_hostname.d.mts +1 -0
  33. package/node_modules/typebox/build/format/idn_hostname.mjs +3 -11
  34. package/node_modules/typebox/build/format/idna/format/bidi.d.mts +3 -0
  35. package/node_modules/typebox/build/format/idna/format/bidi.mjs +98 -0
  36. package/node_modules/typebox/build/format/idna/format/puny.d.mts +3 -0
  37. package/node_modules/typebox/build/format/idna/format/puny.mjs +144 -0
  38. package/node_modules/typebox/build/format/idna/hostname.d.mts +1 -0
  39. package/node_modules/typebox/build/format/idna/hostname.mjs +16 -0
  40. package/node_modules/typebox/build/format/idna/idn-hostname.d.mts +1 -0
  41. package/node_modules/typebox/build/format/idna/idn-hostname.mjs +41 -0
  42. package/node_modules/typebox/build/format/idna/index.d.mts +2 -0
  43. package/node_modules/typebox/build/format/idna/index.mjs +2 -0
  44. package/node_modules/typebox/build/format/idna/label/ascii.d.mts +1 -0
  45. package/node_modules/typebox/build/format/idna/label/ascii.mjs +9 -0
  46. package/node_modules/typebox/build/format/idna/label/puny.d.mts +1 -0
  47. package/node_modules/typebox/build/format/idna/label/puny.mjs +28 -0
  48. package/node_modules/typebox/build/format/idna/label/unicode.d.mts +1 -0
  49. package/node_modules/typebox/build/format/idna/label/unicode.mjs +106 -0
  50. package/node_modules/typebox/build/format/idna/pattern/pattern.d.mts +30 -0
  51. package/node_modules/typebox/build/format/idna/pattern/pattern.mjs +70 -0
  52. package/node_modules/typebox/build/format/ipv4.d.mts +0 -1
  53. package/node_modules/typebox/build/format/ipv4.mjs +2 -32
  54. package/node_modules/typebox/build/format/ipv6.mjs +2 -61
  55. package/node_modules/typebox/build/format/iri.d.mts +2 -2
  56. package/node_modules/typebox/build/format/iri.mjs +26 -8
  57. package/node_modules/typebox/build/format/iri_reference.d.mts +2 -2
  58. package/node_modules/typebox/build/format/iri_reference.mjs +9 -55
  59. package/node_modules/typebox/build/format/regex.d.mts +1 -2
  60. package/node_modules/typebox/build/format/regex.mjs +2 -6
  61. package/node_modules/typebox/build/format/time.d.mts +2 -2
  62. package/node_modules/typebox/build/format/time.mjs +19 -14
  63. package/node_modules/typebox/build/format/uri.d.mts +1 -1
  64. package/node_modules/typebox/build/format/uri.mjs +3 -130
  65. package/node_modules/typebox/build/format/uri_reference.d.mts +1 -1
  66. package/node_modules/typebox/build/format/uri_reference.mjs +2 -3
  67. package/node_modules/typebox/build/format/uri_template.d.mts +2 -3
  68. package/node_modules/typebox/build/format/uri_template.mjs +4 -5
  69. package/node_modules/typebox/build/format/url.d.mts +3 -3
  70. package/node_modules/typebox/build/format/url.mjs +4 -5
  71. package/node_modules/typebox/build/guard/emit.d.mts +3 -1
  72. package/node_modules/typebox/build/guard/emit.mjs +12 -5
  73. package/node_modules/typebox/build/guard/guard.d.mts +8 -2
  74. package/node_modules/typebox/build/guard/guard.mjs +25 -4
  75. package/node_modules/typebox/build/guard/string.d.mts +2 -2
  76. package/node_modules/typebox/build/guard/string.mjs +7 -7
  77. package/node_modules/typebox/build/schema/engine/_context.mjs +3 -3
  78. package/node_modules/typebox/build/schema/engine/_functions.mjs +1 -2
  79. package/node_modules/typebox/build/schema/engine/_regexp.d.mts +2 -0
  80. package/node_modules/typebox/build/schema/engine/_regexp.mjs +4 -0
  81. package/node_modules/typebox/build/schema/engine/_stack.mjs +12 -13
  82. package/node_modules/typebox/build/schema/engine/additionalItems.mjs +17 -7
  83. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +4 -3
  84. package/node_modules/typebox/build/schema/engine/contains.mjs +18 -6
  85. package/node_modules/typebox/build/schema/engine/enum.mjs +1 -1
  86. package/node_modules/typebox/build/schema/engine/items.mjs +26 -6
  87. package/node_modules/typebox/build/schema/engine/maxContains.mjs +3 -3
  88. package/node_modules/typebox/build/schema/engine/minContains.mjs +14 -4
  89. package/node_modules/typebox/build/schema/engine/not.mjs +4 -2
  90. package/node_modules/typebox/build/schema/engine/oneOf.mjs +7 -6
  91. package/node_modules/typebox/build/schema/engine/pattern.mjs +3 -2
  92. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +4 -3
  93. package/node_modules/typebox/build/schema/engine/ref.mjs +1 -7
  94. package/node_modules/typebox/build/schema/engine/schema.mjs +13 -4
  95. package/node_modules/typebox/build/schema/engine/type.mjs +1 -1
  96. package/node_modules/typebox/build/schema/resolve/resolve.d.mts +4 -1
  97. package/node_modules/typebox/build/schema/resolve/resolve.mjs +202 -1
  98. package/node_modules/typebox/build/schema/static/propertyNames.d.mts +5 -0
  99. package/node_modules/typebox/build/schema/static/propertyNames.mjs +3 -0
  100. package/node_modules/typebox/build/schema/static/schema.d.mts +3 -0
  101. package/node_modules/typebox/build/schema/types/index.d.mts +0 -2
  102. package/node_modules/typebox/build/schema/types/index.mjs +0 -2
  103. package/node_modules/typebox/build/system/memory/clone.mjs +14 -11
  104. package/node_modules/typebox/build/system/memory/discard.mjs +6 -5
  105. package/node_modules/typebox/build/system/settings/settings.d.mts +9 -0
  106. package/node_modules/typebox/build/system/settings/settings.mjs +2 -0
  107. package/node_modules/typebox/build/type/engine/call/distribute_arguments.d.mts +6 -1
  108. package/node_modules/typebox/build/type/engine/call/distribute_arguments.mjs +16 -3
  109. package/node_modules/typebox/build/type/engine/call/instantiate.mjs +34 -4
  110. package/node_modules/typebox/build/type/engine/evaluate/broaden.d.mts +5 -5
  111. package/node_modules/typebox/build/type/engine/evaluate/broaden.mjs +20 -26
  112. package/node_modules/typebox/build/type/engine/evaluate/compare.d.mts +6 -10
  113. package/node_modules/typebox/build/type/engine/evaluate/compare.mjs +9 -13
  114. package/node_modules/typebox/build/type/engine/evaluate/composite.d.mts +7 -4
  115. package/node_modules/typebox/build/type/engine/evaluate/composite.mjs +12 -19
  116. package/node_modules/typebox/build/type/engine/evaluate/distribute.d.mts +4 -20
  117. package/node_modules/typebox/build/type/engine/evaluate/distribute.mjs +8 -20
  118. package/node_modules/typebox/build/type/engine/evaluate/evaluate.d.mts +4 -4
  119. package/node_modules/typebox/build/type/engine/evaluate/evaluate.mjs +11 -8
  120. package/node_modules/typebox/build/type/engine/evaluate/flatten.d.mts +1 -1
  121. package/node_modules/typebox/build/type/engine/evaluate/flatten.mjs +3 -4
  122. package/node_modules/typebox/build/type/engine/evaluate/narrow.d.mts +17 -2
  123. package/node_modules/typebox/build/type/engine/evaluate/narrow.mjs +26 -6
  124. package/node_modules/typebox/build/type/engine/exclude/operation.mjs +3 -4
  125. package/node_modules/typebox/build/type/engine/extract/operation.d.mts +1 -1
  126. package/node_modules/typebox/build/type/engine/extract/operation.mjs +3 -4
  127. package/node_modules/typebox/build/type/engine/priority/priority.d.mts +2 -2
  128. package/node_modules/typebox/build/type/engine/priority/priority.mjs +3 -4
  129. package/node_modules/typebox/build/type/script/mapping.d.mts +32 -29
  130. package/node_modules/typebox/build/type/script/mapping.mjs +18 -25
  131. package/node_modules/typebox/build/type/script/parser.d.mts +19 -25
  132. package/node_modules/typebox/build/type/script/parser.mjs +27 -23
  133. package/node_modules/typebox/build/value/errors/errors.d.mts +2 -2
  134. package/node_modules/typebox/build/value/errors/errors.mjs +1 -1
  135. package/node_modules/typebox/package.json +27 -27
  136. package/node_modules/typebox/readme.md +79 -82
  137. package/openclaw.plugin.json +26 -2
  138. package/package.json +6 -6
  139. package/dist/card-command-CyDSm-OU.js +0 -224
  140. package/dist/channel.runtime-qrUV6wPE.js +0 -4
  141. package/dist/config-schema-Be3azjB_.js +0 -47
  142. package/dist/monitor.runtime-B2z_5ugU.js +0 -2
  143. package/dist/outbound.runtime-DDTeqaBW.js +0 -3
  144. package/dist/probe.runtime-D13ZtXsd.js +0 -2
  145. package/dist/quick-reply-fallback-D86FwAfr.js +0 -56
  146. package/node_modules/typebox/build/format/_idna.d.mts +0 -2
  147. package/node_modules/typebox/build/format/_idna.mjs +0 -246
  148. package/node_modules/typebox/build/format/_puny.d.mts +0 -1
  149. package/node_modules/typebox/build/format/_puny.mjs +0 -77
  150. package/node_modules/typebox/build/schema/resolve/ref.d.mts +0 -3
  151. package/node_modules/typebox/build/schema/resolve/ref.mjs +0 -146
  152. package/node_modules/typebox/build/schema/types/contentEncoding.d.mts +0 -9
  153. package/node_modules/typebox/build/schema/types/contentEncoding.mjs +0 -13
  154. package/node_modules/typebox/build/schema/types/contentMediaType.d.mts +0 -9
  155. package/node_modules/typebox/build/schema/types/contentMediaType.mjs +0 -13
  156. package/dist/{probe-CM03wo6r.js → probe-4gc_YQ2L.js} +1 -1
@@ -1,17 +1,17 @@
1
- import { i as resolveLineAccount } from "./accounts-BqEVsDoB.js";
2
- import { h as normalizeLineMessageActions, p as messageAction, t as createLineSendReceipt, x as validateLineMediaUrl } from "./send-receipt-DKZzgHMO.js";
1
+ import { i as resolveLineAccount } from "./accounts-BBEFMYbY.js";
2
+ import { h as normalizeLineMessageActions, i as createLineSendReceipt, p as messageAction, r as runLinePushWithRetries, y as buildLineMediaMessage } from "./send-retry-CUvrarfE.js";
3
3
  import { createChannelPartialDeliveryError } from "openclaw/plugin-sdk/channel-inbound";
4
4
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
5
+ import { HTTPFetchError, messagingApi } from "@line/bot-sdk";
5
6
  import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
6
7
  import { fetchWithRuntimeDispatcherOrMockedGlobal } from "openclaw/plugin-sdk/runtime-fetch";
7
- import { HTTPFetchError, messagingApi } from "@line/bot-sdk";
8
- import { collectErrorGraphCandidates, extractErrorCode } from "openclaw/plugin-sdk/error-runtime";
9
8
  import { randomUUID } from "node:crypto";
10
9
  import lineBotSdkPackage from "@line/bot-sdk/package.json" with { type: "json" };
11
10
  import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
12
11
  import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
13
12
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
14
- import { classifyTransientNetworkErrorCode, createChannelApiRetryRunner } from "openclaw/plugin-sdk/retry-runtime";
13
+ import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
14
+ import { createDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
15
15
  //#region extensions/line/src/channel-access-token.ts
16
16
  function resolveLineChannelAccessToken(explicit, params) {
17
17
  if (explicit?.trim()) return explicit.trim();
@@ -19,35 +19,69 @@ function resolveLineChannelAccessToken(explicit, params) {
19
19
  return params.channelAccessToken.trim();
20
20
  }
21
21
  //#endregion
22
- //#region extensions/line/src/send-retry.ts
23
- function isRetryableLinePushError(error) {
24
- const candidates = collectErrorGraphCandidates(error, (candidate) => [candidate.cause, candidate.error]);
25
- const httpError = candidates.find((candidate) => candidate instanceof HTTPFetchError);
26
- if (httpError) return httpError.status >= 500;
27
- return candidates.some((candidate) => classifyTransientNetworkErrorCode(extractErrorCode(candidate)) !== void 0);
28
- }
29
- /**
30
- * Pushes are non-idempotent without a retry key, so the generic message-matching
31
- * fallback stays off and only the classification above may replay a request.
32
- */
33
- const runLinePushWithRetries = createChannelApiRetryRunner({
34
- shouldRetry: isRetryableLinePushError,
35
- strictShouldRetry: true,
36
- verbose: true
37
- });
22
+ //#region extensions/line/src/outbound-message-log.ts
23
+ const RECENT_SENT_LIMIT = 500;
24
+ const recentSentByAccount = /* @__PURE__ */ new Map();
25
+ function recentSentFor(accountId) {
26
+ const existing = recentSentByAccount.get(accountId);
27
+ if (existing) return existing;
28
+ const created = createDedupeCache({
29
+ ttlMs: 0,
30
+ maxSize: RECENT_SENT_LIMIT
31
+ });
32
+ recentSentByAccount.set(accountId, created);
33
+ return created;
34
+ }
35
+ function recordLineSentMessages(accountId, messageIds) {
36
+ if (messageIds.length === 0) return;
37
+ const recentSent = recentSentFor(accountId);
38
+ for (const messageId of messageIds) recentSent.check(messageId);
39
+ }
40
+ function quotesLineBotMessage(accountId, quotedMessageId) {
41
+ return recentSentByAccount.get(accountId)?.peek(quotedMessageId) ?? false;
42
+ }
38
43
  //#endregion
39
44
  //#region extensions/line/src/send.ts
40
- const userProfileCache = /* @__PURE__ */ new Map();
41
- const PROFILE_CACHE_TTL_MS = 300 * 1e3;
45
+ const profileCache = {
46
+ values: /* @__PURE__ */ new Map(),
47
+ pending: /* @__PURE__ */ new Map()
48
+ };
49
+ const groupNameCache = {
50
+ values: /* @__PURE__ */ new Map(),
51
+ pending: /* @__PURE__ */ new Map()
52
+ };
53
+ const PROFILE_CACHE_TTL_MS = 3e5;
42
54
  const PROFILE_CACHE_MAX_ENTRIES = 1e3;
43
55
  const LINE_FLEX_ALT_TEXT_LIMIT = 1500;
44
56
  const LINE_LOCATION_LABEL_LIMIT = 100;
45
- function cacheUserProfile(userId, profile) {
46
- userProfileCache.delete(userId);
47
- userProfileCache.set(userId, profile);
48
- if (userProfileCache.size <= PROFILE_CACHE_MAX_ENTRIES) return;
49
- for (const [key, cached] of userProfileCache) if (profile.fetchedAt - cached.fetchedAt >= PROFILE_CACHE_TTL_MS) userProfileCache.delete(key);
50
- pruneMapToMaxSize(userProfileCache, PROFILE_CACHE_MAX_ENTRIES);
57
+ const LINE_PROVIDER_RESPONSE_MAX_BYTES = 16384;
58
+ function rememberLineIdentity(cache, key, value) {
59
+ const entry = {
60
+ value,
61
+ fetchedAt: Date.now()
62
+ };
63
+ cache.values.delete(key);
64
+ cache.values.set(key, entry);
65
+ if (cache.values.size <= PROFILE_CACHE_MAX_ENTRIES) return;
66
+ for (const [cachedKey, cached] of cache.values) if (entry.fetchedAt - cached.fetchedAt >= PROFILE_CACHE_TTL_MS) cache.values.delete(cachedKey);
67
+ pruneMapToMaxSize(cache.values, PROFILE_CACHE_MAX_ENTRIES);
68
+ }
69
+ async function loadLineIdentity(cache, key, load) {
70
+ const cached = cache.values.get(key);
71
+ if (cached && Date.now() - cached.fetchedAt < PROFILE_CACHE_TTL_MS) return cached.value;
72
+ const pending = cache.pending.get(key);
73
+ if (pending) return await pending;
74
+ const lookup = load().then((value) => {
75
+ rememberLineIdentity(cache, key, value);
76
+ return value;
77
+ });
78
+ cache.pending.set(key, lookup);
79
+ pruneMapToMaxSize(cache.pending, PROFILE_CACHE_MAX_ENTRIES);
80
+ try {
81
+ return await lookup;
82
+ } finally {
83
+ if (cache.pending.get(key) === lookup) cache.pending.delete(key);
84
+ }
51
85
  }
52
86
  function resolveLineProviderMessageIds(response, operation) {
53
87
  const sentMessages = Array.isArray(response?.sentMessages) ? response.sentMessages : [];
@@ -74,12 +108,10 @@ function normalizeTarget(to) {
74
108
  if (normalized.length >= 33 && !/^[CUR]/.test(normalized)) throw new Error(`Recipient is not a valid LINE id (case-sensitive; expected leading capital C/U/R): ${truncateUtf16Safe(normalized, 4)}…`);
75
109
  return normalized;
76
110
  }
77
- function isLineUserChatId(chatId) {
78
- return /^U/i.test(chatId);
79
- }
80
111
  function resolveLineMessagingAccount(opts) {
112
+ const cfg = requireRuntimeConfig(opts.cfg, "LINE send");
81
113
  const account = resolveLineAccount({
82
- cfg: requireRuntimeConfig(opts.cfg, "LINE send"),
114
+ cfg,
83
115
  accountId: opts.accountId
84
116
  });
85
117
  return {
@@ -114,15 +146,17 @@ async function sendLineProviderMessages(operation, token, request, retryKey) {
114
146
  body: JSON.stringify(request)
115
147
  });
116
148
  const acceptedRetryConflict = retryKey !== void 0 && response.status === 409;
117
- if (!response.ok && !acceptedRetryConflict) throw new HTTPFetchError(`${response.status} - ${response.statusText}`, {
118
- status: response.status,
119
- statusText: response.statusText,
120
- headers: response.headers,
121
- body: await response.text()
122
- });
149
+ if (!response.ok && !acceptedRetryConflict) {
150
+ const body = await readResponseTextLimited(response, LINE_PROVIDER_RESPONSE_MAX_BYTES).catch(() => "");
151
+ throw new HTTPFetchError(`${response.status} - ${response.statusText}`, {
152
+ status: response.status,
153
+ statusText: response.statusText,
154
+ headers: response.headers,
155
+ body
156
+ });
157
+ }
123
158
  try {
124
- const text = await response.text();
125
- return text ? JSON.parse(text) : null;
159
+ return await readProviderJsonResponse(response, `LINE ${operation} response`, { maxBytes: LINE_PROVIDER_RESPONSE_MAX_BYTES });
126
160
  } catch (error) {
127
161
  throw createChannelPartialDeliveryError(error, {
128
162
  messageIds: [],
@@ -136,28 +170,6 @@ function createTextMessage(text) {
136
170
  text
137
171
  };
138
172
  }
139
- function createImageMessage(originalContentUrl, previewImageUrl) {
140
- return {
141
- type: "image",
142
- originalContentUrl,
143
- previewImageUrl: previewImageUrl ?? originalContentUrl
144
- };
145
- }
146
- function createVideoMessage(originalContentUrl, previewImageUrl, trackingId) {
147
- return {
148
- type: "video",
149
- originalContentUrl,
150
- previewImageUrl,
151
- ...trackingId ? { trackingId } : {}
152
- };
153
- }
154
- function createAudioMessage(originalContentUrl, durationMs) {
155
- return {
156
- type: "audio",
157
- originalContentUrl,
158
- duration: durationMs
159
- };
160
- }
161
173
  function isValidLineLocation(location) {
162
174
  return location.title.trim().length > 0 && location.address.trim().length > 0;
163
175
  }
@@ -180,9 +192,13 @@ function createLocationMessage(location) {
180
192
  function logLineHttpError(err, context) {
181
193
  if (!err || typeof err !== "object") return;
182
194
  const { status, statusText, body } = err;
183
- if (typeof body === "string") logVerbose(`line: ${context} failed (${status ? `${status} ${statusText ?? ""}`.trim() : "unknown status"}): ${body}`);
195
+ if (typeof body === "string") {
196
+ const summary = status ? `${status} ${statusText ?? ""}`.trim() : "unknown status";
197
+ logVerbose(`line: ${context} failed (${summary}): ${body}`);
198
+ }
184
199
  }
185
200
  function recordLineOutboundActivity(accountId, delivery) {
201
+ recordLineSentMessages(accountId, delivery.messageIds);
186
202
  try {
187
203
  recordChannelActivity({
188
204
  channel: "line",
@@ -236,7 +252,10 @@ async function pushLineMessages(to, messages, opts, behavior = {}) {
236
252
  messageIds,
237
253
  receipt: result.receipt
238
254
  });
239
- if (opts.verbose) logVerbose(behavior.verboseMessage?.(chatId, messages.length) ?? `line: pushed ${messages.length} messages to ${chatId}`);
255
+ if (opts.verbose) {
256
+ const logMessage = behavior.verboseMessage?.(chatId, messages.length) ?? `line: pushed ${messages.length} messages to ${chatId}`;
257
+ logVerbose(logMessage);
258
+ }
240
259
  return result;
241
260
  }
242
261
  async function replyLineMessages(replyToken, messages, opts) {
@@ -253,29 +272,12 @@ async function sendMessageLine(to, text, opts) {
253
272
  const chatId = normalizeTarget(to);
254
273
  const messages = [];
255
274
  const mediaUrl = opts.mediaUrl?.trim();
256
- if (mediaUrl) {
257
- await validateLineMediaUrl(mediaUrl);
258
- switch (opts.mediaKind) {
259
- case "video": {
260
- const previewImageUrl = opts.previewImageUrl?.trim();
261
- if (!previewImageUrl) throw new Error("LINE video messages require previewImageUrl to reference an image URL");
262
- await validateLineMediaUrl(previewImageUrl);
263
- const trackingId = isLineUserChatId(chatId) ? opts.trackingId : void 0;
264
- messages.push(createVideoMessage(mediaUrl, previewImageUrl, trackingId));
265
- break;
266
- }
267
- case "audio":
268
- messages.push(createAudioMessage(mediaUrl, opts.durationMs ?? 6e4));
269
- break;
270
- default:
271
- {
272
- const previewImageUrl = opts.previewImageUrl?.trim() || mediaUrl;
273
- await validateLineMediaUrl(previewImageUrl);
274
- messages.push(createImageMessage(mediaUrl, previewImageUrl));
275
- }
276
- break;
277
- }
278
- }
275
+ if (mediaUrl) messages.push(await buildLineMediaMessage(mediaUrl, {
276
+ mediaKind: opts.mediaKind,
277
+ previewImageUrl: opts.previewImageUrl,
278
+ durationMs: opts.durationMs,
279
+ trackingId: opts.trackingId
280
+ }, chatId));
279
281
  if (text?.trim()) messages.push(createTextMessage(text.trim()));
280
282
  if (messages.length === 0) throw new Error("Message must be non-empty for LINE sends");
281
283
  if (opts.replyToken) {
@@ -322,9 +324,10 @@ function createFlexMessage(altText, contents) {
322
324
  };
323
325
  }
324
326
  async function pushImageMessage(to, originalContentUrl, previewImageUrl, opts) {
325
- await validateLineMediaUrl(originalContentUrl);
326
- if (previewImageUrl) await validateLineMediaUrl(previewImageUrl);
327
- return pushLineMessages(to, [createImageMessage(originalContentUrl, previewImageUrl)], opts, { verboseMessage: (chatId) => `line: pushed image to ${chatId}` });
327
+ return pushLineMessages(to, [await buildLineMediaMessage(originalContentUrl, {
328
+ mediaKind: "image",
329
+ previewImageUrl
330
+ }, to)], opts, { verboseMessage: (chatId) => `line: pushed image to ${chatId}` });
328
331
  }
329
332
  async function pushLocationMessage(to, location, opts) {
330
333
  return pushLineMessages(to, [createLocationMessage(location)], opts, { verboseMessage: (chatId) => `line: pushed location to ${chatId}` });
@@ -366,26 +369,43 @@ async function showLoadingAnimation(chatId, opts) {
366
369
  logVerbose(`line: loading animation failed (non-fatal): ${String(err)}`);
367
370
  }
368
371
  }
372
+ function lineProfileCacheKey(accountId, userId, scope) {
373
+ const conversation = scope.groupId ? ["group", scope.groupId] : scope.roomId ? ["room", scope.roomId] : ["direct"];
374
+ return JSON.stringify([
375
+ accountId,
376
+ "profile",
377
+ ...conversation,
378
+ userId
379
+ ]);
380
+ }
381
+ function fetchLineMemberProfile(client, userId, scope) {
382
+ if (scope.groupId) return client.getGroupMemberProfile(scope.groupId, userId);
383
+ if (scope.roomId) return client.getRoomMemberProfile(scope.roomId, userId);
384
+ return client.getProfile(userId);
385
+ }
369
386
  async function getUserProfile(userId, opts) {
370
- if (opts.useCache ?? true) {
371
- const cached = userProfileCache.get(userId);
372
- if (cached && Date.now() - cached.fetchedAt < PROFILE_CACHE_TTL_MS) return {
373
- displayName: cached.displayName,
374
- pictureUrl: cached.pictureUrl
375
- };
376
- }
377
- const { client } = createLineMessagingClient(opts);
387
+ const useCache = opts.useCache ?? true;
378
388
  try {
379
- const profile = await client.getProfile(userId);
380
- const result = {
381
- displayName: profile.displayName,
382
- pictureUrl: profile.pictureUrl
389
+ const { account, token } = resolveLineMessagingAccount(opts);
390
+ const cacheKey = lineProfileCacheKey(account.accountId, userId, opts);
391
+ const load = async () => {
392
+ try {
393
+ const profile = await fetchLineMemberProfile(new messagingApi.MessagingApiClient({ channelAccessToken: token }), userId, opts);
394
+ return {
395
+ displayName: profile.displayName,
396
+ pictureUrl: profile.pictureUrl
397
+ };
398
+ } catch (err) {
399
+ logVerbose(`line: failed to fetch profile for ${userId}: ${String(err)}`);
400
+ return null;
401
+ }
383
402
  };
384
- cacheUserProfile(userId, {
385
- ...result,
386
- fetchedAt: Date.now()
387
- });
388
- return result;
403
+ if (!useCache) {
404
+ const profile = await load();
405
+ rememberLineIdentity(profileCache, cacheKey, profile);
406
+ return profile;
407
+ }
408
+ return await loadLineIdentity(profileCache, cacheKey, load);
389
409
  } catch (err) {
390
410
  logVerbose(`line: failed to fetch profile for ${userId}: ${String(err)}`);
391
411
  return null;
@@ -394,5 +414,26 @@ async function getUserProfile(userId, opts) {
394
414
  async function getUserDisplayName(userId, opts) {
395
415
  return (await getUserProfile(userId, opts))?.displayName ?? userId;
396
416
  }
417
+ async function getLineGroupName(groupId, opts) {
418
+ try {
419
+ const { account, token } = resolveLineMessagingAccount(opts);
420
+ const cacheKey = JSON.stringify([
421
+ account.accountId,
422
+ "group",
423
+ groupId
424
+ ]);
425
+ return await loadLineIdentity(groupNameCache, cacheKey, async () => {
426
+ try {
427
+ return (await new messagingApi.MessagingApiClient({ channelAccessToken: token }).getGroupSummary(groupId)).groupName.trim() || void 0;
428
+ } catch (err) {
429
+ logVerbose(`line: failed to fetch group summary for ${groupId}: ${String(err)}`);
430
+ return;
431
+ }
432
+ });
433
+ } catch (err) {
434
+ logVerbose(`line: failed to fetch group summary for ${groupId}: ${String(err)}`);
435
+ return;
436
+ }
437
+ }
397
438
  //#endregion
398
- export { replyMessageLine as _, createQuickReplyItems as a, resolveLineChannelAccessToken as b, getUserDisplayName as c, pushImageMessage as d, pushLocationMessage as f, pushTextMessageWithQuickReplies as g, pushTemplateMessage as h, createLocationMessage as i, getUserProfile as l, pushMessagesLine as m, createFlexMessage as n, createTextMessageWithQuickReplies as o, pushMessageLine as p, createImageMessage as r, createVideoMessage as s, createAudioMessage as t, pushFlexMessage as u, sendMessageLine as v, showLoadingAnimation as y };
439
+ export { showLoadingAnimation as _, getLineGroupName as a, pushFlexMessage as c, pushMessageLine as d, pushMessagesLine as f, sendMessageLine as g, replyMessageLine as h, createTextMessageWithQuickReplies as i, pushImageMessage as l, pushTextMessageWithQuickReplies as m, createLocationMessage as n, getUserDisplayName as o, pushTemplateMessage as p, createQuickReplyItems as r, getUserProfile as s, createFlexMessage as t, pushLocationMessage as u, quotesLineBotMessage as v, resolveLineChannelAccessToken as y };
package/dist/setup-api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as lineSetupAdapter, t as lineSetupWizard } from "./setup-surface-W0Dbgu58.js";
1
+ import { n as lineSetupAdapter, t as lineSetupWizard } from "./setup-surface-CDlgkTKi.js";
2
2
  export { lineSetupAdapter, lineSetupWizard };
@@ -1,19 +1,9 @@
1
- import { i as resolveLineAccount, n as normalizeAccountId, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-BqEVsDoB.js";
1
+ import { i as resolveLineAccount, n as normalizeAccountId, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-BBEFMYbY.js";
2
+ import { n as parseLineAllowFromId, t as hasLineCredentials } from "./account-helpers-BSUyZwLm.js";
2
3
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
3
4
  import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
4
5
  import { DEFAULT_ACCOUNT_ID, createAllowFromSection, createPromptParsedAllowFromForAccount, createSetupInputPresenceValidator, createSetupTranslator, createStandardChannelSetupStatus, defineTokenCredential, formatDocsLink, parseSetupEntriesWithParser, patchScopedAccountConfig, setSetupChannelEnabled, splitSetupEntries } from "openclaw/plugin-sdk/setup";
5
6
  import { createChannelDmPolicy } from "openclaw/plugin-sdk/channel-dm-policy";
6
- //#region extensions/line/src/account-helpers.ts
7
- function hasLineCredentials(account) {
8
- if (account.tokenStatus && account.signingSecretStatus) return account.tokenStatus !== "missing" && account.signingSecretStatus !== "missing";
9
- return Boolean(account.channelAccessToken?.trim() && account.channelSecret?.trim());
10
- }
11
- function parseLineAllowFromId(raw) {
12
- const trimmed = raw.trim().replace(/^line:(?:user:)?/i, "");
13
- if (!/^U[a-f0-9]{32}$/i.test(trimmed)) return null;
14
- return trimmed;
15
- }
16
- //#endregion
17
7
  //#region extensions/line/src/setup-core.ts
18
8
  function patchLineAccountConfig(params) {
19
9
  return patchScopedAccountConfig({
@@ -62,6 +52,26 @@ const lineSetupAdapter = {
62
52
  const accessToken = typedInput.channelAccessToken ?? typedInput.token;
63
53
  const normalizedAccountId = normalizeAccountId(accountId);
64
54
  const useEnv = normalizedAccountId === DEFAULT_ACCOUNT_ID && Boolean(typedInput.useEnv);
55
+ const credentials = [{
56
+ fileKey: "tokenFile",
57
+ file: typedInput.tokenFile,
58
+ inlineKey: "channelAccessToken",
59
+ inline: accessToken
60
+ }, {
61
+ fileKey: "secretFile",
62
+ file: typedInput.secretFile,
63
+ inlineKey: "channelSecret",
64
+ inline: typedInput.channelSecret
65
+ }];
66
+ const patch = {};
67
+ const retired = [];
68
+ for (const credential of credentials) if (credential.file) {
69
+ patch[credential.fileKey] = credential.file;
70
+ retired.push(credential.inlineKey);
71
+ } else if (credential.inline) {
72
+ patch[credential.inlineKey] = credential.inline;
73
+ retired.push(credential.fileKey);
74
+ }
65
75
  return patchLineAccountConfig({
66
76
  cfg,
67
77
  accountId: normalizedAccountId,
@@ -71,11 +81,8 @@ const lineSetupAdapter = {
71
81
  "channelSecret",
72
82
  "tokenFile",
73
83
  "secretFile"
74
- ] : void 0,
75
- patch: useEnv ? {} : {
76
- ...typedInput.tokenFile ? { tokenFile: typedInput.tokenFile } : accessToken ? { channelAccessToken: accessToken } : {},
77
- ...typedInput.secretFile ? { secretFile: typedInput.secretFile } : typedInput.channelSecret ? { channelSecret: typedInput.channelSecret } : {}
78
- }
84
+ ] : retired.length > 0 ? retired : void 0,
85
+ patch: useEnv ? {} : patch
79
86
  });
80
87
  }
81
88
  };
@@ -152,6 +159,27 @@ const LINE_ALLOW_FROM_HELP_LINES = [
152
159
  t("wizard.line.multipleEntries"),
153
160
  t("wizard.channels.docs", { link: formatDocsLink("/channels/line", "channels/line") })
154
161
  ];
162
+ const promptLineAllowFrom = createPromptParsedAllowFromForAccount({
163
+ defaultAccountId: resolveDefaultLineAccountId,
164
+ noteTitle: t("wizard.line.allowlistTitle"),
165
+ noteLines: LINE_ALLOW_FROM_HELP_LINES,
166
+ message: t("wizard.line.allowFromPrompt"),
167
+ placeholder: "U1234567890abcdef1234567890abcdef",
168
+ parseEntries: (raw) => parseSetupEntriesWithParser(raw, (entry) => {
169
+ const id = parseLineAllowFromId(entry);
170
+ return id ? { value: id } : { error: t("wizard.line.allowFromInvalid") };
171
+ }),
172
+ getExistingAllowFrom: ({ cfg, accountId }) => resolveLineAccount({
173
+ cfg,
174
+ accountId
175
+ }).config.allowFrom ?? [],
176
+ applyAllowFrom: ({ cfg, accountId, allowFrom }) => patchLineAccountConfig({
177
+ cfg,
178
+ accountId,
179
+ enabled: true,
180
+ patch: { allowFrom }
181
+ })
182
+ });
155
183
  const lineDmPolicy = createChannelDmPolicy({
156
184
  label: "LINE",
157
185
  channel,
@@ -166,27 +194,7 @@ const lineDmPolicy = createChannelDmPolicy({
166
194
  patch,
167
195
  clearFields: patch.dmPolicy === "pairing" || patch.dmPolicy === "disabled" ? ["allowFrom"] : void 0
168
196
  }),
169
- promptAllowFrom: createPromptParsedAllowFromForAccount({
170
- defaultAccountId: resolveDefaultLineAccountId,
171
- noteTitle: t("wizard.line.allowlistTitle"),
172
- noteLines: LINE_ALLOW_FROM_HELP_LINES,
173
- message: t("wizard.line.allowFromPrompt"),
174
- placeholder: "U1234567890abcdef1234567890abcdef",
175
- parseEntries: (raw) => parseSetupEntriesWithParser(raw, (entry) => {
176
- const id = parseLineAllowFromId(entry);
177
- return id ? { value: id } : { error: t("wizard.line.allowFromInvalid") };
178
- }),
179
- getExistingAllowFrom: ({ cfg, accountId }) => resolveLineAccount({
180
- cfg,
181
- accountId
182
- }).config.allowFrom ?? [],
183
- applyAllowFrom: ({ cfg, accountId, allowFrom }) => patchLineAccountConfig({
184
- cfg,
185
- accountId,
186
- enabled: true,
187
- patch: { allowFrom }
188
- })
189
- })
197
+ promptAllowFrom: promptLineAllowFrom
190
198
  });
191
199
  function createLineTokenCredential(params) {
192
200
  return defineTokenCredential({
@@ -295,4 +303,4 @@ const lineSetupWizard = {
295
303
  disable: (cfg) => setSetupChannelEnabled(cfg, channel, false)
296
304
  };
297
305
  //#endregion
298
- export { hasLineCredentials as i, lineSetupAdapter as n, lineSetupContract as r, lineSetupWizard as t };
306
+ export { lineSetupAdapter as n, lineSetupContract as r, lineSetupWizard as t };
@@ -0,0 +1,59 @@
1
+ import { isRecord, normalizeNullableString } from "openclaw/plugin-sdk/string-coerce-runtime";
2
+ //#region extensions/line/src/webhook-spool-contract.ts
3
+ /** Message the canonical decoder attaches when it rejects a spool payload. The
4
+ * upgrade migration matches this signature to recover rows the pre-fix decoder
5
+ * dead-lettered; the identity fence writes a different message on purpose. */
6
+ const LINE_WEBHOOK_SPOOL_INVALID_PAYLOAD_MESSAGE = "LINE webhook spool payload is invalid.";
7
+ /** Dead-letter reason for undecodable events; shared so the migration's
8
+ * recovery signature can never drift from what the spool writes. */
9
+ const LINE_WEBHOOK_SPOOL_INVALID_EVENT_REASON = "invalid-event";
10
+ /** Defined locally (not via createChannelIngressError) because this module sits
11
+ * in the doctor contract closure, which must stay off the channel-outbound
12
+ * runtime barrel; the spool's permanent-failure classifier matches by class. */
13
+ var LineWebhookPayloadError = class extends Error {
14
+ constructor(message, options) {
15
+ super(message, options);
16
+ this.name = "LineWebhookPayloadError";
17
+ }
18
+ };
19
+ /** Message ids preserve the shipped replay-guard keyspace; other events use LINE's delivery id. */
20
+ function eventIdFor(event) {
21
+ if (!isRecord(event)) throw new LineWebhookPayloadError("LINE webhook event must be an object.");
22
+ if (event.type === "message") {
23
+ const message = isRecord(event.message) ? event.message : void 0;
24
+ const messageId = normalizeNullableString(message?.id);
25
+ if (messageId) return `message:${messageId}`;
26
+ }
27
+ const webhookEventId = normalizeNullableString(event.webhookEventId);
28
+ if (webhookEventId) return `event:${webhookEventId}`;
29
+ throw new LineWebhookPayloadError("LINE webhook event is missing a stable delivery id.");
30
+ }
31
+ /** Pre-drain (#109655) rows were keyed by the raw webhookEventId, before the
32
+ * message:/event: keyspace. The upgrade migration uses this prior derivation as its
33
+ * identity fence so a genuinely changed event still dead-letters. */
34
+ function legacyEventIdFor(event) {
35
+ if (!isRecord(event)) return null;
36
+ return normalizeNullableString(event.webhookEventId);
37
+ }
38
+ function laneKeyFor(event, eventId) {
39
+ if (!isRecord(event)) return eventId;
40
+ const source = isRecord(event.source) ? event.source : void 0;
41
+ if (source?.type === "group") {
42
+ const groupId = normalizeNullableString(source.groupId);
43
+ if (groupId) return `group:${groupId}`;
44
+ }
45
+ if (source?.type === "room") {
46
+ const roomId = normalizeNullableString(source.roomId);
47
+ if (roomId) return `room:${roomId}`;
48
+ }
49
+ if (source?.type === "user") {
50
+ const userId = normalizeNullableString(source.userId);
51
+ if (userId) return `user:${userId}`;
52
+ }
53
+ return eventId;
54
+ }
55
+ function errorText(error) {
56
+ return error instanceof Error ? error.message : String(error);
57
+ }
58
+ //#endregion
59
+ export { eventIdFor as a, errorText as i, LINE_WEBHOOK_SPOOL_INVALID_PAYLOAD_MESSAGE as n, laneKeyFor as o, LineWebhookPayloadError as r, legacyEventIdFor as s, LINE_WEBHOOK_SPOOL_INVALID_EVENT_REASON as t };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Returns true if the value is a ISO8601 Date component string
3
- * @source ajv-formats
3
+ * @description https://datatracker.ietf.org/doc/html/rfc3339
4
4
  * @example `2020-12-12`
5
5
  */
6
6
  export declare function IsDate(value: string): boolean;
@@ -5,7 +5,7 @@ function IsLeapYear(year) {
5
5
  }
6
6
  /**
7
7
  * Returns true if the value is a ISO8601 Date component string
8
- * @source ajv-formats
8
+ * @description https://datatracker.ietf.org/doc/html/rfc3339
9
9
  * @example `2020-12-12`
10
10
  */
11
11
  export function IsDate(value) {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Returns true if the value is a ISO8601 DateTime string
3
- * @source ajv-formats
3
+ * @specification https://datatracker.ietf.org/doc/html/rfc3339
4
4
  * @example `2020-12-12T20:20:40+00:00`
5
5
  */
6
- export declare function IsDateTime(value: string, strictTimeZone?: boolean): boolean;
6
+ export declare function IsDateTime(value: string): boolean;
@@ -2,10 +2,10 @@ import { IsDate } from './date.mjs';
2
2
  import { IsTime } from './time.mjs';
3
3
  /**
4
4
  * Returns true if the value is a ISO8601 DateTime string
5
- * @source ajv-formats
5
+ * @specification https://datatracker.ietf.org/doc/html/rfc3339
6
6
  * @example `2020-12-12T20:20:40+00:00`
7
7
  */
8
- export function IsDateTime(value, strictTimeZone = true) {
8
+ export function IsDateTime(value) {
9
9
  const dateTime = value.split(/T/i);
10
- return dateTime.length === 2 && IsDate(dateTime[0]) && IsTime(dateTime[1], strictTimeZone);
10
+ return dateTime.length === 2 && IsDate(dateTime[0]) && IsTime(dateTime[1]);
11
11
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Returns true if the value is an Email
3
- * @specification ajv-formats
3
+ * @specification https://datatracker.ietf.org/doc/html/rfc5322
4
4
  */
5
5
  export declare function IsEmail(value: string): boolean;
@@ -1,7 +1,7 @@
1
- const Email = /^(?!.*\.\.)[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i;
1
+ const Email = /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[^"\\]|\\[\x20-\x7e])*")@(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*|\[(?:IPv6:[a-f0-9:]+|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})\])$/i;
2
2
  /**
3
3
  * Returns true if the value is an Email
4
- * @specification ajv-formats
4
+ * @specification https://datatracker.ietf.org/doc/html/rfc5322
5
5
  */
6
6
  export function IsEmail(value) {
7
7
  return Email.test(value);
@@ -1,4 +1,4 @@
1
- import * as Idna from './_idna.mjs';
1
+ import * as Idna from './idna/index.mjs';
2
2
  /**
3
3
  * Returns true if the value is a valid hostname.
4
4
  * @specification https://tools.ietf.org/html/rfc1123
@@ -6,13 +6,5 @@ import * as Idna from './_idna.mjs';
6
6
  * @specification https://tools.ietf.org/html/rfc5892
7
7
  */
8
8
  export function IsHostname(value) {
9
- if (value.length === 0 || value.length > 253)
10
- return false;
11
- if (value.charCodeAt(value.length - 1) === 46)
12
- return false;
13
- for (const label of value.split('.')) {
14
- if (!Idna.IsLabel(label))
15
- return false;
16
- }
17
- return true;
9
+ return Idna.IsHostname(value);
18
10
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Returns true if the value is an IdnEmail
3
- * @specification ajv-formats (unicode-extension)
3
+ * @specification https://datatracker.ietf.org/doc/html/rfc4952
4
4
  */
5
5
  export declare function IsIdnEmail(value: string): boolean;
@@ -1,8 +1,8 @@
1
- const IdnEmail = /^(?!.*\.\.)[\p{L}\p{N}!#$%&'*+/=?^_`{|}~-]+(?:\.[\p{L}\p{N}!#$%&'*+/=?^_`{|}~-]+)*@[\p{L}\p{N}](?:[\p{L}\p{N}-]{0,61}[\p{L}\p{N}])?(?:\.[\p{L}\p{N}](?:[\p{L}\p{N}-]{0,61}[\p{L}\p{N}])?)*$/iu;
1
+ const IdnEmail = /^(?:[A-Za-z0-9!#$%&'*+\/=?^_`{|}~\u{0080}-\u{10FFFF}-]+(?:\.[A-Za-z0-9!#$%&'*+\/=?^_`{|}~\u{0080}-\u{10FFFF}-]+)*|"(?:[^"\\]|\\.)*")@[\p{L}\p{N}](?:[\p{L}\p{N}-]{0,62})(?<!-)(?:\.[\p{L}\p{N}](?:[\p{L}\p{N}-]{0,62})(?<!-))*$/iu;
2
2
  /**
3
3
  * Returns true if the value is an IdnEmail
4
- * @specification ajv-formats (unicode-extension)
4
+ * @specification https://datatracker.ietf.org/doc/html/rfc4952
5
5
  */
6
6
  export function IsIdnEmail(value) {
7
- return IdnEmail.test(value);
7
+ return IdnEmail.test(value.normalize('NFC'));
8
8
  }
@@ -3,5 +3,6 @@
3
3
  * @specification https://tools.ietf.org/html/rfc3490
4
4
  * @specification https://tools.ietf.org/html/rfc5891
5
5
  * @specification https://tools.ietf.org/html/rfc5892
6
+ * @specification https://tools.ietf.org/html/rfc5893
6
7
  */
7
8
  export declare function IsIdnHostname(value: string): boolean;