@openclaw/line 2026.9.8 → 2026.10.1-beta.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.
- package/dist/.setup/api-BBSLQek1.mjs +3 -0
- package/dist/.setup/{api-w4QdlCCB.mjs → api-CMU7bG-C.mjs} +2 -2
- package/dist/.setup/{card-command-DYf4gnmI.mjs → card-command-BrU6u6pm.mjs} +2 -2
- package/dist/.setup/{channel-DN6Za8Vm.mjs → channel-InKuXaRS.mjs} +32 -78
- package/dist/.setup/{channel.runtime-bd5vGeSo.mjs → channel.runtime-BEhASjr_.mjs} +3 -3
- package/dist/.setup/{monitor-C01hAm3f.mjs → monitor-Bb3HieCd.mjs} +92 -160
- package/dist/.setup/monitor.runtime-C6Kgs5BI.mjs +2 -0
- package/dist/.setup/{outbound.runtime-D_Gs99Dm.mjs → outbound.runtime-BwO_m4n5.mjs} +2 -2
- package/dist/.setup/probe.runtime-C6LYzOHu.mjs +2 -0
- package/dist/.setup/{send-DOY1WTxv.mjs → send-DAwROcyQ.mjs} +10 -16
- package/dist/.setup/{send-retry-DV6I0muq.mjs → send-retry-B9Nor0-J.mjs} +54 -86
- package/dist/.setup/{setup-surface-CbfkmkIS.mjs → setup-surface-1jkHGWg7.mjs} +7 -2
- package/dist/.setup/{template-messages-CxCnYsWN.mjs → template-messages-DdF9zv_j.mjs} +103 -120
- package/dist/.setup/{webhook-spool-contract-1rY8OJw8.mjs → webhook-spool-contract-msjTl60Q.mjs} +1 -4
- package/dist/api.js +2 -2
- package/dist/channel-plugin-api.js +1 -1
- package/dist/doctor-contract-api.js +5 -4
- package/dist/index.js +1 -1
- package/dist/setup-api.js +1 -1
- package/dist/setup-entry.js +1 -1
- package/package.json +4 -4
- package/dist/.setup/api-4RgpQmHW.mjs +0 -3
- package/dist/.setup/monitor.runtime-DwH2zK48.mjs +0 -2
- package/dist/.setup/probe.runtime-Cj08-BZ1.mjs +0 -2
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { n as resolveDefaultLineAccountId, r as resolveLineAccount } from "./accounts-DOiM8-y3.mjs";
|
|
2
|
-
import { a as resolveLineQuestionPostback,
|
|
2
|
+
import { a as resolveLineQuestionPostback, i as parseLineQuestionPostbackData, n as createLineQuickReply, o as buildLineQuickReplyFallbackText, r as prepareLineReplyPayload, s as resolveLineGroupConfigEntry } from "./channel-InKuXaRS.mjs";
|
|
3
|
+
import { a as normalizeLineAllowEntry } from "./setup-surface-1jkHGWg7.mjs";
|
|
3
4
|
import { t as getLineRuntime } from "./runtime-V7yh9OeC.mjs";
|
|
4
|
-
import { D as recordLineQuoteToken, E as readLineQuoteToken, O as reportLineQuoteCarrierMissing, T as canCarryLineQuoteToken, j as buildLineMediaMessage, k as resolveLineQuoteToken, n as findLineHttpError, r as resolveLineNonDispatchRetryable, t as explainLineRefusal, w as applyLineQuoteToken } from "./send-retry-
|
|
5
|
-
import { a as
|
|
6
|
-
import { a as getUserDisplayName, g as quotesLineBotMessage, h as showLoadingAnimation, i as getLineGroupName, l as pushMessageLine, n as createLocationMessage, o as getUserProfile, p as replyMessageLine, t as createFlexMessage, u as pushMessagesLine } from "./send-
|
|
7
|
-
import { n as processLineMessage, t as buildTemplateMessageFromPayload } from "./template-messages-
|
|
5
|
+
import { D as recordLineQuoteToken, E as readLineQuoteToken, O as reportLineQuoteCarrierMissing, T as canCarryLineQuoteToken, j as buildLineMediaMessage, k as resolveLineQuoteToken, n as findLineHttpError, r as resolveLineNonDispatchRetryable, t as explainLineRefusal, w as applyLineQuoteToken } from "./send-retry-B9Nor0-J.mjs";
|
|
6
|
+
import { a as laneKeyFor, i as eventIdFor, n as LINE_WEBHOOK_SPOOL_INVALID_PAYLOAD_MESSAGE, r as LineWebhookPayloadError, t as LINE_WEBHOOK_SPOOL_INVALID_EVENT_REASON } from "./webhook-spool-contract-msjTl60Q.mjs";
|
|
7
|
+
import { a as getUserDisplayName, g as quotesLineBotMessage, h as showLoadingAnimation, i as getLineGroupName, l as pushMessageLine, n as createLocationMessage, o as getUserProfile, p as replyMessageLine, t as createFlexMessage, u as pushMessagesLine } from "./send-DAwROcyQ.mjs";
|
|
8
|
+
import { n as processLineMessage, t as buildTemplateMessageFromPayload } from "./template-messages-DdF9zv_j.mjs";
|
|
8
9
|
import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
|
|
9
10
|
import { resolveChannelGroupsConfigPath } from "openclaw/plugin-sdk/channel-policy";
|
|
10
11
|
import { normalizeOptionalString, normalizeStringEntries, readNonEmptyStringPreservingWhitespace } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
@@ -16,7 +17,7 @@ import { collectErrorGraphCandidates, extractErrorCode, formatErrorMessage, read
|
|
|
16
17
|
import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
|
17
18
|
import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
|
|
18
19
|
import { createNonExitingRuntime, danger, logVerbose, shouldLogVerbose, waitForAbortSignal, warn } from "openclaw/plugin-sdk/runtime-env";
|
|
19
|
-
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
20
|
+
import { safeParseJson, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
20
21
|
import { classifyTransientNetworkErrorCode } from "openclaw/plugin-sdk/retry-runtime";
|
|
21
22
|
import { fetchWithRuntimeDispatcherOrMockedGlobal } from "openclaw/plugin-sdk/runtime-fetch";
|
|
22
23
|
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
@@ -40,6 +41,7 @@ import { getSessionBindingService, inspectRuntimeConversationBindingRoute } from
|
|
|
40
41
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
41
42
|
import { MediaFetchError } from "openclaw/plugin-sdk/media-runtime";
|
|
42
43
|
import { saveMediaStream } from "openclaw/plugin-sdk/media-store";
|
|
44
|
+
import { raceWithTimeout } from "openclaw/plugin-sdk/time-runtime";
|
|
43
45
|
import { enqueueKeyedTask } from "openclaw/plugin-sdk/keyed-async-queue";
|
|
44
46
|
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
45
47
|
//#region extensions/line/src/auto-reply-delivery.ts
|
|
@@ -144,16 +146,10 @@ async function deliverLineAutoReply(params) {
|
|
|
144
146
|
}
|
|
145
147
|
if (lineData.location) richMessages.push(createLocationMessage(lineData.location));
|
|
146
148
|
const visibleText = payload.text ? sanitizeAssistantVisibleText(payload.text) : "";
|
|
147
|
-
const
|
|
148
|
-
text: "",
|
|
149
|
-
flexMessages: []
|
|
150
|
-
};
|
|
151
|
-
if (!processed.segments) for (const flexMsg of processed.flexMessages) richMessages.push(createFlexMessage(flexMsg.altText, flexMsg.contents));
|
|
152
|
-
const orderedMessages = processed.segments?.flatMap((segment) => segment.type === "flex" ? [createFlexMessage(segment.message.altText, segment.message.contents)] : chunkMarkdownText(segment.text, textLimit).map((text) => ({
|
|
149
|
+
const markdownMessages = (visibleText ? processLineMessage(visibleText) : []).flatMap((segment) => segment.type === "flex" ? [createFlexMessage(segment.message.altText, segment.message.contents)] : chunkMarkdownText(segment.text, textLimit).map((text) => ({
|
|
153
150
|
type: "text",
|
|
154
151
|
text
|
|
155
152
|
})));
|
|
156
|
-
const chunks = orderedMessages ? orderedMessages.flatMap((message) => message.type === "text" ? [message.text] : []) : processed.text ? chunkMarkdownText(processed.text, textLimit) : [];
|
|
157
153
|
const mediaUrls = resolveSendableOutboundReplyParts(payload).mediaUrls;
|
|
158
154
|
const mediaOpts = {
|
|
159
155
|
mediaKind: lineData.mediaKind,
|
|
@@ -163,23 +159,16 @@ async function deliverLineAutoReply(params) {
|
|
|
163
159
|
};
|
|
164
160
|
const mediaMessages = [];
|
|
165
161
|
let deliveryError;
|
|
166
|
-
for (const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
mediaMessages.push(await buildLineMediaMessage(url, mediaOpts, to));
|
|
171
|
-
} catch (err) {
|
|
172
|
-
deliveryError ??= err;
|
|
173
|
-
}
|
|
162
|
+
for (const url of mediaUrls) try {
|
|
163
|
+
mediaMessages.push(await buildLineMediaMessage(url, mediaOpts, to));
|
|
164
|
+
} catch (err) {
|
|
165
|
+
deliveryError ??= err;
|
|
174
166
|
}
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
text
|
|
178
|
-
}));
|
|
179
|
-
const orderedDeliveryMessages = hasQuickReplies && chunks.length === 0 ? void 0 : orderedMessages;
|
|
167
|
+
const inlineMarkdown = hasQuickReplies && !markdownMessages.some((message) => message.type === "text");
|
|
168
|
+
const textMessages = inlineMarkdown ? [] : markdownMessages;
|
|
180
169
|
const richMediaMessages = [
|
|
181
170
|
...richMessages,
|
|
182
|
-
...
|
|
171
|
+
...inlineMarkdown ? markdownMessages : [],
|
|
183
172
|
...mediaMessages
|
|
184
173
|
];
|
|
185
174
|
if (hasQuickReplies && textMessages.length === 0 && richMediaMessages.length === 0) textMessages.push({
|
|
@@ -187,14 +176,14 @@ async function deliverLineAutoReply(params) {
|
|
|
187
176
|
text: buildLineQuickReplyFallbackText(quickReplyLabels)
|
|
188
177
|
});
|
|
189
178
|
if (hasQuickReplies) {
|
|
190
|
-
const targetMessages =
|
|
179
|
+
const targetMessages = textMessages.length > 0 ? textMessages : richMediaMessages;
|
|
191
180
|
const lastIndex = targetMessages.length - 1;
|
|
192
181
|
targetMessages[lastIndex] = {
|
|
193
182
|
...expectDefined(targetMessages[lastIndex], "last LINE auto-reply message"),
|
|
194
183
|
quickReply: createLineQuickReply(quickReplyItems)
|
|
195
184
|
};
|
|
196
185
|
}
|
|
197
|
-
const ordered = hasQuickReplies ? [...richMediaMessages, ...
|
|
186
|
+
const ordered = hasQuickReplies ? [...richMediaMessages, ...textMessages] : [...textMessages, ...richMediaMessages];
|
|
198
187
|
const replyQuoteToken = resolveLineQuoteToken({
|
|
199
188
|
cfg: params.cfg,
|
|
200
189
|
accountId,
|
|
@@ -296,7 +285,7 @@ function getLineSourceInfo(source) {
|
|
|
296
285
|
isGroup: false
|
|
297
286
|
};
|
|
298
287
|
return {
|
|
299
|
-
userId: source.type === "user"
|
|
288
|
+
userId: source.type === "user" || source.type === "group" || source.type === "room" ? source.userId : void 0,
|
|
300
289
|
groupId: source.type === "group" ? source.groupId : void 0,
|
|
301
290
|
roomId: source.type === "room" ? source.roomId : void 0,
|
|
302
291
|
isGroup: source.type === "group" || source.type === "room"
|
|
@@ -373,7 +362,8 @@ async function resolveLineInboundRoute(params) {
|
|
|
373
362
|
accountId: params.account.accountId,
|
|
374
363
|
direction: "inbound"
|
|
375
364
|
});
|
|
376
|
-
const
|
|
365
|
+
const prepared = params.preparedRoute ?? await prepareLineInboundRoute(params);
|
|
366
|
+
const { peerId, runtimeRoute, configuredBinding, configuredBindingSessionKey } = prepared;
|
|
377
367
|
if (runtimeRoute.bindingRecord) {
|
|
378
368
|
await getSessionBindingService().touchAsync(runtimeRoute.bindingRecord.bindingId, void 0, runtimeRoute.bindingRecord.conversation);
|
|
379
369
|
logVerbose(runtimeRoute.boundSessionKey ? `line: routed via bound conversation ${peerId} -> ${runtimeRoute.boundSessionKey}` : `line: plugin-bound conversation ${peerId}`);
|
|
@@ -389,14 +379,7 @@ async function resolveLineInboundRoute(params) {
|
|
|
389
379
|
}
|
|
390
380
|
logVerbose(`line: using configured ACP binding for ${peerId} -> ${configuredBindingSessionKey}`);
|
|
391
381
|
}
|
|
392
|
-
return
|
|
393
|
-
userId,
|
|
394
|
-
groupId,
|
|
395
|
-
roomId,
|
|
396
|
-
isGroup,
|
|
397
|
-
peerId,
|
|
398
|
-
route
|
|
399
|
-
};
|
|
382
|
+
return prepared;
|
|
400
383
|
}
|
|
401
384
|
/**
|
|
402
385
|
* Describe a sticker from what its webhook actually carries: LINE sends up to
|
|
@@ -429,9 +412,9 @@ function extractMessageText(message) {
|
|
|
429
412
|
}
|
|
430
413
|
function extractNativeMediaKind(message) {
|
|
431
414
|
switch (message.type) {
|
|
432
|
-
case "image":
|
|
433
|
-
case "video":
|
|
434
|
-
case "audio": return
|
|
415
|
+
case "image":
|
|
416
|
+
case "video":
|
|
417
|
+
case "audio": return message.type;
|
|
435
418
|
case "file": return "document";
|
|
436
419
|
default: return;
|
|
437
420
|
}
|
|
@@ -568,22 +551,21 @@ async function finalizeLineInboundContext(params) {
|
|
|
568
551
|
}
|
|
569
552
|
async function buildLineMessageContext(params) {
|
|
570
553
|
const { event, allMedia, mediaUnavailable, cfg, account, commandAuthorized, inboundHistory } = params;
|
|
571
|
-
const source =
|
|
572
|
-
|
|
573
|
-
source,
|
|
554
|
+
const source = await resolveLineInboundRoute({
|
|
555
|
+
source: event.source,
|
|
574
556
|
cfg,
|
|
575
557
|
account,
|
|
576
558
|
preparedRoute: params.preparedRoute
|
|
577
559
|
});
|
|
560
|
+
const { peerId, route } = source;
|
|
578
561
|
const message = event.message;
|
|
579
562
|
const messageId = message.id;
|
|
580
|
-
const
|
|
563
|
+
const rawBody = extractMessageText(message);
|
|
581
564
|
const nativeMediaKind = extractNativeMediaKind(message);
|
|
582
565
|
const mediaFacts = allMedia.length > 0 ? allMedia.map((media) => ({
|
|
583
566
|
...media,
|
|
584
567
|
kind: nativeMediaKind
|
|
585
568
|
})) : nativeMediaKind ? [{ kind: nativeMediaKind }] : [];
|
|
586
|
-
const rawBody = textContent;
|
|
587
569
|
const shortfallNotice = params.missingParts ? `[line: ${params.missingParts === 1 ? "1 more image in this send was" : `${params.missingParts} more images in this send were`} not delivered]` : void 0;
|
|
588
570
|
const withShortfall = shortfallNotice ? formatInboundMediaUnavailableText({
|
|
589
571
|
body: rawBody,
|
|
@@ -601,27 +583,18 @@ async function buildLineMessageContext(params) {
|
|
|
601
583
|
quoteToken: readLineQuoteToken(message)
|
|
602
584
|
});
|
|
603
585
|
let locationContext;
|
|
604
|
-
if (message.type === "location") {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
address: loc.address
|
|
611
|
-
});
|
|
612
|
-
}
|
|
586
|
+
if (message.type === "location") locationContext = toLocationContext({
|
|
587
|
+
latitude: message.latitude,
|
|
588
|
+
longitude: message.longitude,
|
|
589
|
+
name: message.title,
|
|
590
|
+
address: message.address
|
|
591
|
+
});
|
|
613
592
|
return finalizeLineInboundContext({
|
|
614
593
|
cfg,
|
|
615
594
|
account,
|
|
616
595
|
event,
|
|
617
596
|
route,
|
|
618
|
-
source
|
|
619
|
-
userId,
|
|
620
|
-
groupId,
|
|
621
|
-
roomId,
|
|
622
|
-
isGroup,
|
|
623
|
-
peerId
|
|
624
|
-
},
|
|
597
|
+
source,
|
|
625
598
|
rawBody,
|
|
626
599
|
agentBody,
|
|
627
600
|
commandBody: resolveLineMentionStrippedText(message) || rawBody,
|
|
@@ -646,13 +619,12 @@ async function buildLineMessageContext(params) {
|
|
|
646
619
|
}
|
|
647
620
|
async function buildLinePostbackContext(params) {
|
|
648
621
|
const { event, cfg, account, commandAuthorized } = params;
|
|
649
|
-
const source =
|
|
650
|
-
|
|
651
|
-
source,
|
|
622
|
+
const source = await resolveLineInboundRoute({
|
|
623
|
+
source: event.source,
|
|
652
624
|
cfg,
|
|
653
625
|
account
|
|
654
626
|
});
|
|
655
|
-
const
|
|
627
|
+
const { route } = source;
|
|
656
628
|
const rawBody = event.postback?.data?.trim() ?? "";
|
|
657
629
|
if (!rawBody) return null;
|
|
658
630
|
let agentBody = rawBody;
|
|
@@ -666,19 +638,13 @@ async function buildLinePostbackContext(params) {
|
|
|
666
638
|
const picked = normalizeOptionalString(value);
|
|
667
639
|
if (picked) agentBody += ` ${key}=${picked}`;
|
|
668
640
|
}
|
|
669
|
-
const messageSid =
|
|
641
|
+
const messageSid = `postback:${event.replyToken || event.timestamp}`;
|
|
670
642
|
return finalizeLineInboundContext({
|
|
671
643
|
cfg,
|
|
672
644
|
account,
|
|
673
645
|
event,
|
|
674
646
|
route,
|
|
675
|
-
source
|
|
676
|
-
userId,
|
|
677
|
-
groupId,
|
|
678
|
-
roomId,
|
|
679
|
-
isGroup,
|
|
680
|
-
peerId
|
|
681
|
-
},
|
|
647
|
+
source,
|
|
682
648
|
rawBody,
|
|
683
649
|
agentBody,
|
|
684
650
|
messageSid,
|
|
@@ -1274,37 +1240,34 @@ async function handleLineWebhookEvents(events, context) {
|
|
|
1274
1240
|
const [event, ...setParts] = events;
|
|
1275
1241
|
if (!event) return;
|
|
1276
1242
|
try {
|
|
1277
|
-
|
|
1243
|
+
switch (event.type) {
|
|
1244
|
+
case "message":
|
|
1245
|
+
await handleMessageEvent(event, context, setParts.filter((part) => part.type === "message"));
|
|
1246
|
+
break;
|
|
1247
|
+
case "follow":
|
|
1248
|
+
case "unfollow": {
|
|
1249
|
+
const { userId } = getLineSourceInfo(event.source);
|
|
1250
|
+
logVerbose(`line: user ${userId ?? "unknown"} ${event.type}ed`);
|
|
1251
|
+
break;
|
|
1252
|
+
}
|
|
1253
|
+
case "join":
|
|
1254
|
+
await handleJoinEvent(event, context);
|
|
1255
|
+
break;
|
|
1256
|
+
case "leave": {
|
|
1257
|
+
const { groupId, roomId } = getLineSourceInfo(event.source);
|
|
1258
|
+
logVerbose(`line: bot left ${groupId ? `group ${groupId}` : `room ${roomId}`}`);
|
|
1259
|
+
break;
|
|
1260
|
+
}
|
|
1261
|
+
case "postback":
|
|
1262
|
+
await handlePostbackEvent(event, context);
|
|
1263
|
+
break;
|
|
1264
|
+
default: logVerbose(`line: unhandled event type: ${event.type}`);
|
|
1265
|
+
}
|
|
1278
1266
|
} catch (err) {
|
|
1279
1267
|
context.runtime.error?.(danger(`line: event handler failed: ${String(err)}`));
|
|
1280
1268
|
throw toErrorObject(err, "Non-Error thrown");
|
|
1281
1269
|
}
|
|
1282
1270
|
}
|
|
1283
|
-
async function handleLineWebhookEvent(event, context, setParts = []) {
|
|
1284
|
-
switch (event.type) {
|
|
1285
|
-
case "message":
|
|
1286
|
-
await handleMessageEvent(event, context, setParts.filter((part) => part.type === "message"));
|
|
1287
|
-
break;
|
|
1288
|
-
case "follow":
|
|
1289
|
-
case "unfollow": {
|
|
1290
|
-
const { userId } = getLineSourceInfo(event.source);
|
|
1291
|
-
logVerbose(`line: user ${userId ?? "unknown"} ${event.type}ed`);
|
|
1292
|
-
break;
|
|
1293
|
-
}
|
|
1294
|
-
case "join":
|
|
1295
|
-
await handleJoinEvent(event, context);
|
|
1296
|
-
break;
|
|
1297
|
-
case "leave": {
|
|
1298
|
-
const { groupId, roomId } = getLineSourceInfo(event.source);
|
|
1299
|
-
logVerbose(`line: bot left ${groupId ? `group ${groupId}` : `room ${roomId}`}`);
|
|
1300
|
-
break;
|
|
1301
|
-
}
|
|
1302
|
-
case "postback":
|
|
1303
|
-
await handlePostbackEvent(event, context);
|
|
1304
|
-
break;
|
|
1305
|
-
default: logVerbose(`line: unhandled event type: ${event.type}`);
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
1271
|
//#endregion
|
|
1309
1272
|
//#region extensions/line/src/inbound-image-set.ts
|
|
1310
1273
|
const IMAGE_SET_FLUSH_DELAY_MS = 4e3;
|
|
@@ -1425,17 +1388,6 @@ function isLineAuthenticationFailure(error) {
|
|
|
1425
1388
|
const status = error.status;
|
|
1426
1389
|
return status === 401 || status === 403;
|
|
1427
1390
|
}
|
|
1428
|
-
async function waitForActiveDeliveriesBeforeDispose(activeDeliveries) {
|
|
1429
|
-
let timeout;
|
|
1430
|
-
try {
|
|
1431
|
-
return await Promise.race([Promise.allSettled(activeDeliveries).then(() => true), new Promise((resolve) => {
|
|
1432
|
-
timeout = setTimeout(() => resolve(false), LINE_WEBHOOK_ACTIVE_DELIVERY_STOP_GRACE_MS);
|
|
1433
|
-
timeout.unref?.();
|
|
1434
|
-
})]);
|
|
1435
|
-
} finally {
|
|
1436
|
-
if (timeout) clearTimeout(timeout);
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
1391
|
/** The imageSet a LINE inbound event belongs to, when it reported one. */
|
|
1440
1392
|
function senderKeyFor(event) {
|
|
1441
1393
|
const source = event.source;
|
|
@@ -1638,7 +1590,7 @@ function createLineWebhookSpool(options) {
|
|
|
1638
1590
|
stopTask ??= (async () => {
|
|
1639
1591
|
await monitor.pause();
|
|
1640
1592
|
try {
|
|
1641
|
-
const deliveriesSettled = await
|
|
1593
|
+
const deliveriesSettled = await raceWithTimeout(Promise.allSettled(activeDeliveries).then(() => true), LINE_WEBHOOK_ACTIVE_DELIVERY_STOP_GRACE_MS, () => false, { ref: false });
|
|
1642
1594
|
if (!deliveriesSettled) options.runtime.log(warn(`line: timed out after ${LINE_WEBHOOK_ACTIVE_DELIVERY_STOP_GRACE_MS}ms waiting for active webhook deliveries; releasing drain ownership`));
|
|
1643
1595
|
await monitor.waitForDeferredClaims();
|
|
1644
1596
|
acceptsDeferredClaims = false;
|
|
@@ -1721,27 +1673,6 @@ function validateLineSignature(body, signature, channelSecret) {
|
|
|
1721
1673
|
return safeEqualSecret(signature, hash);
|
|
1722
1674
|
}
|
|
1723
1675
|
//#endregion
|
|
1724
|
-
//#region extensions/line/src/webhook-utils.ts
|
|
1725
|
-
/** Route the gateway serves when an account configures no `webhookPath`. */
|
|
1726
|
-
const LINE_DEFAULT_WEBHOOK_PATH = "/line/webhook";
|
|
1727
|
-
/** The route this account's monitor serves, which is the one an operator has to register
|
|
1728
|
-
* with LINE. Every surface resolves it here so a warning cannot name a path the gateway
|
|
1729
|
-
* does not answer on. Route registration canonicalizes further, and matches requests the
|
|
1730
|
-
* same way, so a path resolved here always reaches the route the monitor registered. */
|
|
1731
|
-
function resolveLineWebhookPath(webhookPath) {
|
|
1732
|
-
return resolveWebhookPath({
|
|
1733
|
-
webhookPath,
|
|
1734
|
-
defaultPath: LINE_DEFAULT_WEBHOOK_PATH
|
|
1735
|
-
}) ?? LINE_DEFAULT_WEBHOOK_PATH;
|
|
1736
|
-
}
|
|
1737
|
-
function parseLineWebhookBody(rawBody) {
|
|
1738
|
-
try {
|
|
1739
|
-
return JSON.parse(rawBody);
|
|
1740
|
-
} catch {
|
|
1741
|
-
return null;
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
//#endregion
|
|
1745
1676
|
//#region extensions/line/src/webhook-node.ts
|
|
1746
1677
|
const LINE_WEBHOOK_MAX_BODY_BYTES = 1048576;
|
|
1747
1678
|
const LINE_WEBHOOK_PREAUTH_MAX_BODY_BYTES = 65536;
|
|
@@ -1765,6 +1696,11 @@ async function rejectLineWebhookRequest(req, res, error) {
|
|
|
1765
1696
|
await sendHttpRequestRejection(req, res, error.statusCode, JSON.stringify({ error: requestBodyErrorToText(error.code) }), "application/json");
|
|
1766
1697
|
return true;
|
|
1767
1698
|
}
|
|
1699
|
+
function sendLineWebhookJson(res, statusCode, body) {
|
|
1700
|
+
res.statusCode = statusCode;
|
|
1701
|
+
res.setHeader("Content-Type", "application/json");
|
|
1702
|
+
res.end(JSON.stringify(body));
|
|
1703
|
+
}
|
|
1768
1704
|
function createLineNodeWebhookHandler(params) {
|
|
1769
1705
|
const maxBodyBytes = params.maxBodyBytes ?? LINE_WEBHOOK_MAX_BODY_BYTES;
|
|
1770
1706
|
const readBody = params.readBody ?? readLineWebhookRequestBody;
|
|
@@ -1787,10 +1723,8 @@ function createLineNodeWebhookHandler(params) {
|
|
|
1787
1723
|
return;
|
|
1788
1724
|
}
|
|
1789
1725
|
if (req.method !== "POST") {
|
|
1790
|
-
res.statusCode = 405;
|
|
1791
1726
|
res.setHeader("Allow", "GET, HEAD, POST");
|
|
1792
|
-
res
|
|
1793
|
-
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
1727
|
+
sendLineWebhookJson(res, 405, { error: "Method Not Allowed" });
|
|
1794
1728
|
return;
|
|
1795
1729
|
}
|
|
1796
1730
|
try {
|
|
@@ -1798,53 +1732,53 @@ function createLineNodeWebhookHandler(params) {
|
|
|
1798
1732
|
const signature = typeof signatureHeader === "string" ? signatureHeader.trim() : Array.isArray(signatureHeader) ? (signatureHeader[0] ?? "").trim() : "";
|
|
1799
1733
|
if (!signature) {
|
|
1800
1734
|
logVerbose("line: webhook missing X-Line-Signature header");
|
|
1801
|
-
res
|
|
1802
|
-
res.setHeader("Content-Type", "application/json");
|
|
1803
|
-
res.end(JSON.stringify({ error: "Missing X-Line-Signature header" }));
|
|
1735
|
+
sendLineWebhookJson(res, 400, { error: "Missing X-Line-Signature header" });
|
|
1804
1736
|
return;
|
|
1805
1737
|
}
|
|
1806
1738
|
const rawBody = await readBody(req, Math.min(maxBodyBytes, LINE_WEBHOOK_PREAUTH_MAX_BODY_BYTES), LINE_WEBHOOK_PREAUTH_BODY_TIMEOUT_MS);
|
|
1807
1739
|
const match = resolveSingleWebhookTarget(targets, (target) => validateLineSignature(rawBody, signature, target.channelSecret));
|
|
1808
1740
|
if (match.kind === "none") {
|
|
1809
1741
|
logVerbose("line: webhook signature validation failed");
|
|
1810
|
-
res
|
|
1811
|
-
res.setHeader("Content-Type", "application/json");
|
|
1812
|
-
res.end(JSON.stringify({ error: "Invalid signature" }));
|
|
1742
|
+
sendLineWebhookJson(res, 401, { error: "Invalid signature" });
|
|
1813
1743
|
return;
|
|
1814
1744
|
}
|
|
1815
1745
|
if (match.kind === "ambiguous") {
|
|
1816
1746
|
logVerbose("line: webhook signature matched multiple accounts");
|
|
1817
|
-
res
|
|
1818
|
-
res.setHeader("Content-Type", "application/json");
|
|
1819
|
-
res.end(JSON.stringify({ error: "Ambiguous webhook target" }));
|
|
1747
|
+
sendLineWebhookJson(res, 401, { error: "Ambiguous webhook target" });
|
|
1820
1748
|
return;
|
|
1821
1749
|
}
|
|
1822
|
-
const body =
|
|
1750
|
+
const body = safeParseJson(rawBody);
|
|
1823
1751
|
if (!body) {
|
|
1824
|
-
res
|
|
1825
|
-
res.setHeader("Content-Type", "application/json");
|
|
1826
|
-
res.end(JSON.stringify({ error: "Invalid webhook payload" }));
|
|
1752
|
+
sendLineWebhookJson(res, 400, { error: "Invalid webhook payload" });
|
|
1827
1753
|
return;
|
|
1828
1754
|
}
|
|
1829
1755
|
if (body.events && body.events.length > 0) {
|
|
1830
1756
|
logVerbose(`line: received ${body.events.length} webhook events`);
|
|
1831
1757
|
if (await match.target.bot.handleWebhook(body) === "durable") res.setHeader("x-openclaw-delivery-accepted", "durable");
|
|
1832
1758
|
}
|
|
1833
|
-
res
|
|
1834
|
-
res.setHeader("Content-Type", "application/json");
|
|
1835
|
-
res.end(JSON.stringify({ status: "ok" }));
|
|
1759
|
+
sendLineWebhookJson(res, 200, { status: "ok" });
|
|
1836
1760
|
} catch (err) {
|
|
1837
1761
|
if (await rejectLineWebhookRequest(req, res, err)) return;
|
|
1838
1762
|
params.runtime.error?.(danger(`line webhook error: ${formatErrorMessage(err)}`));
|
|
1839
|
-
if (!res.headersSent) {
|
|
1840
|
-
res.statusCode = 500;
|
|
1841
|
-
res.setHeader("Content-Type", "application/json");
|
|
1842
|
-
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
1843
|
-
}
|
|
1763
|
+
if (!res.headersSent) sendLineWebhookJson(res, 500, { error: "Internal server error" });
|
|
1844
1764
|
}
|
|
1845
1765
|
};
|
|
1846
1766
|
}
|
|
1847
1767
|
//#endregion
|
|
1768
|
+
//#region extensions/line/src/webhook-utils.ts
|
|
1769
|
+
/** Route the gateway serves when an account configures no `webhookPath`. */
|
|
1770
|
+
const LINE_DEFAULT_WEBHOOK_PATH = "/line/webhook";
|
|
1771
|
+
/** The route this account's monitor serves, which is the one an operator has to register
|
|
1772
|
+
* with LINE. Every surface resolves it here so a warning cannot name a path the gateway
|
|
1773
|
+
* does not answer on. Route registration canonicalizes further, and matches requests the
|
|
1774
|
+
* same way, so a path resolved here always reaches the route the monitor registered. */
|
|
1775
|
+
function resolveLineWebhookPath(webhookPath) {
|
|
1776
|
+
return resolveWebhookPath({
|
|
1777
|
+
webhookPath,
|
|
1778
|
+
defaultPath: LINE_DEFAULT_WEBHOOK_PATH
|
|
1779
|
+
}) ?? LINE_DEFAULT_WEBHOOK_PATH;
|
|
1780
|
+
}
|
|
1781
|
+
//#endregion
|
|
1848
1782
|
//#region extensions/line/src/monitor.ts
|
|
1849
1783
|
const lineWebhookInFlightLimiter = createWebhookInFlightLimiter();
|
|
1850
1784
|
async function registerLineWebhookTarget(params, bot) {
|
|
@@ -1884,8 +1818,6 @@ async function monitorLineProvider(opts) {
|
|
|
1884
1818
|
if (!token) throw new Error("LINE webhook mode requires a non-empty channel access token.");
|
|
1885
1819
|
if (!secret) throw new Error("LINE webhook mode requires a non-empty channel secret.");
|
|
1886
1820
|
const bot = createLineBot({
|
|
1887
|
-
channelAccessToken: token,
|
|
1888
|
-
channelSecret: secret,
|
|
1889
1821
|
accountId,
|
|
1890
1822
|
runtime,
|
|
1891
1823
|
buildContext,
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { c as pushLocationMessage, d as pushTemplateMessage, f as pushTextMessageWithQuickReplies, l as pushMessageLine, m as sendMessageLine, n as createLocationMessage, r as createQuickReplyItems, s as pushFlexMessage, t as createFlexMessage, u as pushMessagesLine } from "./send-
|
|
2
|
-
import { n as processLineMessage, t as buildTemplateMessageFromPayload } from "./template-messages-
|
|
1
|
+
import { c as pushLocationMessage, d as pushTemplateMessage, f as pushTextMessageWithQuickReplies, l as pushMessageLine, m as sendMessageLine, n as createLocationMessage, r as createQuickReplyItems, s as pushFlexMessage, t as createFlexMessage, u as pushMessagesLine } from "./send-DAwROcyQ.mjs";
|
|
2
|
+
import { n as processLineMessage, t as buildTemplateMessageFromPayload } from "./template-messages-DdF9zv_j.mjs";
|
|
3
3
|
export { buildTemplateMessageFromPayload, createFlexMessage, createLocationMessage, createQuickReplyItems, processLineMessage, pushFlexMessage, pushLocationMessage, pushMessageLine, pushMessagesLine, pushTemplateMessage, pushTextMessageWithQuickReplies, sendMessageLine };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as resolveLineAccount } from "./accounts-DOiM8-y3.mjs";
|
|
2
|
-
import { A as withoutLineQuoteTokens,
|
|
2
|
+
import { A as withoutLineQuoteTokens, _ as messageAction, i as runLinePushWithRetries, j as buildLineMediaMessage, n as findLineHttpError, o as resolveLineChannelAccessToken, s as createLineSendReceipt, v as normalizeLineMessage, w as applyLineQuoteToken } from "./send-retry-B9Nor0-J.mjs";
|
|
3
3
|
import { createChannelPartialDeliveryError } from "openclaw/plugin-sdk/channel-inbound";
|
|
4
4
|
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
5
5
|
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
@@ -159,12 +159,6 @@ async function postLineProviderMessages(operation, token, request, retryKey, aut
|
|
|
159
159
|
});
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
-
function createTextMessage(text) {
|
|
163
|
-
return {
|
|
164
|
-
type: "text",
|
|
165
|
-
text
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
162
|
function isValidLineLocation(location) {
|
|
169
163
|
return location.title.trim().length > 0 && location.address.trim().length > 0;
|
|
170
164
|
}
|
|
@@ -274,7 +268,10 @@ async function sendMessageLine(to, text, opts) {
|
|
|
274
268
|
durationMs: opts.durationMs,
|
|
275
269
|
trackingId: opts.trackingId
|
|
276
270
|
}, chatId));
|
|
277
|
-
if (text?.trim()) messages.push(
|
|
271
|
+
if (text?.trim()) messages.push({
|
|
272
|
+
type: "text",
|
|
273
|
+
text: text.trim()
|
|
274
|
+
});
|
|
278
275
|
if (messages.length === 0) throw new Error("Message must be non-empty for LINE sends");
|
|
279
276
|
if (opts.replyToken) {
|
|
280
277
|
const { messageId, messageIds, accountId } = await replyLineMessages(opts.replyToken, messages, opts);
|
|
@@ -332,7 +329,11 @@ async function pushTemplateMessage(to, template, opts) {
|
|
|
332
329
|
return pushLineMessages(to, [template], opts, { verboseMessage: (chatId) => `line: pushed template message to ${chatId}` });
|
|
333
330
|
}
|
|
334
331
|
async function pushTextMessageWithQuickReplies(to, text, quickReplyLabels, opts) {
|
|
335
|
-
return pushLineMessages(to, [
|
|
332
|
+
return pushLineMessages(to, [{
|
|
333
|
+
type: "text",
|
|
334
|
+
text,
|
|
335
|
+
quickReply: createQuickReplyItems(quickReplyLabels)
|
|
336
|
+
}], opts, { verboseMessage: (chatId) => `line: pushed message with quick replies to ${chatId}` });
|
|
336
337
|
}
|
|
337
338
|
function createQuickReplyItems(labels) {
|
|
338
339
|
return { items: labels.slice(0, 13).map((label) => ({
|
|
@@ -340,13 +341,6 @@ function createQuickReplyItems(labels) {
|
|
|
340
341
|
action: messageAction(label, label)
|
|
341
342
|
})) };
|
|
342
343
|
}
|
|
343
|
-
function createTextMessageWithQuickReplies(text, quickReplyLabels) {
|
|
344
|
-
return {
|
|
345
|
-
type: "text",
|
|
346
|
-
text,
|
|
347
|
-
quickReply: createQuickReplyItems(quickReplyLabels)
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
344
|
async function showLoadingAnimation(chatId, opts) {
|
|
351
345
|
const { token } = resolveLineMessagingAccount(opts);
|
|
352
346
|
const client = new messagingApi.MessagingApiClient({ channelAccessToken: token });
|