@openclaw/line 2026.7.2-beta.7 → 2026.8.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/api.js +2 -3
- package/dist/{card-command-ty11OJNt.js → card-command-DALdUSnc.js} +14 -22
- package/dist/{channel-D4i_K4fQ.js → channel-CAdQJRw7.js} +74 -76
- package/dist/channel-plugin-api.js +1 -1
- package/dist/channel.runtime-CuKoy9xw.js +4 -0
- package/dist/{flex-templates-iKJsyCq-.js → flex-templates-BBBlNqpV.js} +108 -81
- package/dist/index.js +1 -1
- package/dist/markdown-to-line-D4I-6lm6.js +499 -0
- package/dist/{monitor-DXEodYFJ.js → monitor-QEh_ZqyR.js} +94 -43
- package/dist/monitor.runtime-72isajCp.js +2 -0
- package/dist/outbound.runtime-CWQMIE7N.js +3 -0
- package/dist/{reply-payload-transform-D5-dHB8b.js → reply-payload-transform-BOPGHVPo.js} +4 -3
- package/dist/runtime-api.js +6 -5
- package/dist/send-CFFr0-rZ.js +366 -0
- package/dist/setup-api.js +1 -1
- package/dist/{setup-surface-DRQ7n0En.js → setup-surface-W0Dbgu58.js} +2 -1
- package/openclaw.plugin.json +7 -0
- package/package.json +9 -5
- package/dist/channel.runtime-BdhK8Xw7.js +0 -4
- package/dist/markdown-to-line-DMDQTMhx.js +0 -799
- package/dist/monitor.runtime-CZquR7jI.js +0 -2
- package/dist/outbound.runtime-BS0IhU_M.js +0 -2
- package/dist/send-receipt-CY2ey8XH.js +0 -107
package/dist/api.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { n as lineChannelPluginCommon, t as linePlugin } from "./channel-
|
|
2
|
-
import {
|
|
1
|
+
import { n as lineChannelPluginCommon, t as linePlugin } from "./channel-CAdQJRw7.js";
|
|
2
|
+
import { r as lineSetupContract, t as lineSetupWizard } from "./setup-surface-W0Dbgu58.js";
|
|
3
3
|
//#region extensions/line/src/channel.setup.ts
|
|
4
4
|
const lineSetupPlugin = {
|
|
5
5
|
id: "line",
|
|
6
6
|
...lineChannelPluginCommon,
|
|
7
7
|
setupWizard: lineSetupWizard,
|
|
8
|
-
setup: lineSetupAdapter,
|
|
9
8
|
setupContract: lineSetupContract
|
|
10
9
|
};
|
|
11
10
|
//#endregion
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as createImageCard, f as messageAction, g as uriAction, h as postbackAction, l as createInfoCard, o as createReceiptCard, s as createActionCard, u as createListCard } from "./flex-templates-BBBlNqpV.js";
|
|
2
|
+
import { n as createFlexMessage } from "./send-CFFr0-rZ.js";
|
|
2
3
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
3
|
-
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
4
4
|
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
5
5
|
//#region extensions/line/src/card-command.ts
|
|
6
6
|
const CARD_USAGE = `Usage: /card <type> "title" "body" [options]
|
|
@@ -21,6 +21,13 @@ Examples:
|
|
|
21
21
|
function buildLineReply(lineData) {
|
|
22
22
|
return { channelData: { line: lineData } };
|
|
23
23
|
}
|
|
24
|
+
function buildLineFlexReply(altText, contents) {
|
|
25
|
+
const message = createFlexMessage(altText, contents);
|
|
26
|
+
return buildLineReply({ flexMessage: {
|
|
27
|
+
altText: message.altText,
|
|
28
|
+
contents: message.contents
|
|
29
|
+
} });
|
|
30
|
+
}
|
|
24
31
|
/**
|
|
25
32
|
* Parse action string format: "Label|data,Label2|data2"
|
|
26
33
|
* Data can be a URL (uri action) or plain text (message action) or key=value (postback)
|
|
@@ -118,40 +125,28 @@ function registerLineCardCommand(api) {
|
|
|
118
125
|
case "info": {
|
|
119
126
|
const [title = "Info", body = "", footer] = args;
|
|
120
127
|
const bubble = createInfoCard(title, body, footer);
|
|
121
|
-
return
|
|
122
|
-
altText: truncateUtf16Safe(`${title}: ${body}`, 400),
|
|
123
|
-
contents: bubble
|
|
124
|
-
} });
|
|
128
|
+
return buildLineFlexReply(`${title}: ${body}`, bubble);
|
|
125
129
|
}
|
|
126
130
|
case "image": {
|
|
127
131
|
const [title = "Image", caption = ""] = args;
|
|
128
132
|
const imageUrl = flags.url || flags.image;
|
|
129
133
|
if (!imageUrl) return { text: "Error: Image card requires --url <image-url>" };
|
|
130
134
|
const bubble = createImageCard(imageUrl, title, caption);
|
|
131
|
-
return
|
|
132
|
-
altText: truncateUtf16Safe(`${title}: ${caption}`, 400),
|
|
133
|
-
contents: bubble
|
|
134
|
-
} });
|
|
135
|
+
return buildLineFlexReply(`${title}: ${caption}`, bubble);
|
|
135
136
|
}
|
|
136
137
|
case "action": {
|
|
137
138
|
const [title = "Actions", body = ""] = args;
|
|
138
139
|
const actions = parseActions(flags.actions);
|
|
139
140
|
if (actions.length === 0) return { text: "Error: Action card requires --actions \"Label1|data1,Label2|data2\"" };
|
|
140
141
|
const bubble = createActionCard(title, body, actions, { imageUrl: flags.url || flags.image });
|
|
141
|
-
return
|
|
142
|
-
altText: truncateUtf16Safe(`${title}: ${body}`, 400),
|
|
143
|
-
contents: bubble
|
|
144
|
-
} });
|
|
142
|
+
return buildLineFlexReply(`${title}: ${body}`, bubble);
|
|
145
143
|
}
|
|
146
144
|
case "list": {
|
|
147
145
|
const [title = "List", itemsStr = ""] = args;
|
|
148
146
|
const items = parseListItems(itemsStr || flags.items || "");
|
|
149
147
|
if (items.length === 0) return { text: "Error: List card requires items. Usage: /card list \"Title\" \"Item1|Desc1,Item2|Desc2\"" };
|
|
150
148
|
const bubble = createListCard(title, items);
|
|
151
|
-
return
|
|
152
|
-
altText: truncateUtf16Safe(`${title}: ${items.map((i) => i.title).join(", ")}`, 400),
|
|
153
|
-
contents: bubble
|
|
154
|
-
} });
|
|
149
|
+
return buildLineFlexReply(`${title}: ${items.map((item) => item.title).join(", ")}`, bubble);
|
|
155
150
|
}
|
|
156
151
|
case "receipt": {
|
|
157
152
|
const [title = "Receipt", itemsStr = ""] = args;
|
|
@@ -168,10 +163,7 @@ function registerLineCardCommand(api) {
|
|
|
168
163
|
total,
|
|
169
164
|
footer
|
|
170
165
|
});
|
|
171
|
-
return
|
|
172
|
-
altText: truncateUtf16Safe(`${title}: ${items.map((i) => `${i.name} ${i.value}`).join(", ")}`, 400),
|
|
173
|
-
contents: bubble
|
|
174
|
-
} });
|
|
166
|
+
return buildLineFlexReply(`${title}: ${items.map((item) => `${item.name} ${item.value}`).join(", ")}`, bubble);
|
|
175
167
|
}
|
|
176
168
|
case "confirm": {
|
|
177
169
|
const [question = "Confirm?"] = args;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { i as resolveLineAccount, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-BqEVsDoB.js";
|
|
2
|
-
import { i as hasLineCredentials,
|
|
2
|
+
import { i as hasLineCredentials, r as lineSetupContract, t as lineSetupWizard } from "./setup-surface-W0Dbgu58.js";
|
|
3
3
|
import { i as resolveExactLineGroupConfigKey, n as getLineRuntime, t as buildLineQuickReplyFallbackText } from "./quick-reply-fallback-D86FwAfr.js";
|
|
4
|
-
import { n as parseLineDirectives, r as LineChannelConfigSchema, t as hasLineDirectives } from "./reply-payload-transform-
|
|
5
|
-
import {
|
|
4
|
+
import { n as parseLineDirectives, r as LineChannelConfigSchema, t as hasLineDirectives } from "./reply-payload-transform-BOPGHVPo.js";
|
|
5
|
+
import { _ as createLineSendReceipt, b as resolveLineOutboundMedia, v as buildLineMediaMessage, y as hasLineSpecificMediaOptions } from "./flex-templates-BBBlNqpV.js";
|
|
6
6
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
7
7
|
import { buildDmGroupAccountAllowlistAdapter, createFlatAllowlistOverrideResolver } from "openclaw/plugin-sdk/allowlist-config-edit";
|
|
8
8
|
import { buildChannelOutboundSessionRoute, createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
|
|
@@ -14,11 +14,11 @@ import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1 } from "openclaw/plugin-sdk/
|
|
|
14
14
|
import { createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
|
15
15
|
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
16
16
|
import { clearAccountEntryFields } from "openclaw/plugin-sdk/core";
|
|
17
|
-
import { defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-outbound";
|
|
17
|
+
import { createAccountStatusSink, defineChannelMessageAdapter, listMessageReceiptPlatformIds } from "openclaw/plugin-sdk/channel-outbound";
|
|
18
|
+
import { createChannelPartialDeliveryError } from "openclaw/plugin-sdk/channel-inbound";
|
|
18
19
|
import { createAttachedChannelResultAdapter, createEmptyChannelResult } from "openclaw/plugin-sdk/channel-send-result";
|
|
19
20
|
import { resolveOutboundMediaUrls } from "openclaw/plugin-sdk/reply-payload";
|
|
20
21
|
import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
|
|
21
|
-
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
22
22
|
import { buildTokenChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState, createDependentCredentialStatusIssueCollector } from "openclaw/plugin-sdk/status-helpers";
|
|
23
23
|
//#region extensions/line/src/bindings.ts
|
|
24
24
|
function normalizeLineConversationId(raw) {
|
|
@@ -71,9 +71,11 @@ const lineConfigAdapter = createScopedChannelConfigAdapter({
|
|
|
71
71
|
}),
|
|
72
72
|
defaultAccountId: resolveDefaultLineAccountId,
|
|
73
73
|
clearBaseFields: [
|
|
74
|
+
"channelAccessToken",
|
|
74
75
|
"channelSecret",
|
|
75
76
|
"tokenFile",
|
|
76
|
-
"secretFile"
|
|
77
|
+
"secretFile",
|
|
78
|
+
"name"
|
|
77
79
|
],
|
|
78
80
|
resolveAllowFrom: (account) => account.config.allowFrom,
|
|
79
81
|
formatAllowFrom: (allowFrom) => normalizeStringEntries(allowFrom).map(normalizeLineAllowFrom)
|
|
@@ -118,14 +120,19 @@ const lineChannelPluginCommon = {
|
|
|
118
120
|
//#endregion
|
|
119
121
|
//#region extensions/line/src/gateway.ts
|
|
120
122
|
const loadLineProbeRuntime$1 = createLazyRuntimeModule(() => import("./probe.runtime-D13ZtXsd.js"));
|
|
121
|
-
const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-
|
|
123
|
+
const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-72isajCp.js"));
|
|
122
124
|
const lineGatewayAdapter = {
|
|
123
125
|
startAccount: async (ctx) => {
|
|
124
126
|
const account = ctx.account;
|
|
127
|
+
const statusSink = createAccountStatusSink({
|
|
128
|
+
accountId: account.accountId,
|
|
129
|
+
setStatus: ctx.setStatus
|
|
130
|
+
});
|
|
125
131
|
const token = account.channelAccessToken.trim();
|
|
126
132
|
const secret = account.channelSecret.trim();
|
|
127
133
|
if (!token) throw new Error(`LINE webhook mode requires a non-empty channel access token for account "${account.accountId}".`);
|
|
128
134
|
if (!secret) throw new Error(`LINE webhook mode requires a non-empty channel secret for account "${account.accountId}".`);
|
|
135
|
+
statusSink({ lifecycle: "starting" });
|
|
129
136
|
let lineBotLabel = "";
|
|
130
137
|
try {
|
|
131
138
|
const probe = await (await loadLineProbeRuntime$1()).probeLineBot(token, 2500);
|
|
@@ -141,8 +148,10 @@ const lineGatewayAdapter = {
|
|
|
141
148
|
accountId: account.accountId,
|
|
142
149
|
config: ctx.cfg,
|
|
143
150
|
runtime: ctx.runtime,
|
|
151
|
+
buildContext: ctx.channelRuntime?.inbound.buildContext,
|
|
144
152
|
abortSignal: ctx.abortSignal,
|
|
145
|
-
webhookPath: account.config.webhookPath
|
|
153
|
+
webhookPath: account.config.webhookPath,
|
|
154
|
+
statusSink
|
|
146
155
|
});
|
|
147
156
|
},
|
|
148
157
|
logoutAccount: async ({ accountId, cfg }) => {
|
|
@@ -233,8 +242,7 @@ function inferLineTargetChatType(target) {
|
|
|
233
242
|
}
|
|
234
243
|
//#endregion
|
|
235
244
|
//#region extensions/line/src/outbound.ts
|
|
236
|
-
const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-
|
|
237
|
-
const LINE_FLEX_ALT_TEXT_LIMIT = 1500;
|
|
245
|
+
const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-CWQMIE7N.js"));
|
|
238
246
|
const lineOutboundAdapter = {
|
|
239
247
|
deliveryMode: "direct",
|
|
240
248
|
chunker: (text, limit) => getLineRuntime().channel.text.chunkMarkdownText(text, limit),
|
|
@@ -245,6 +253,9 @@ const lineOutboundAdapter = {
|
|
|
245
253
|
const outboundRuntime = await loadLineOutboundRuntime();
|
|
246
254
|
const lineData = payload.channelData?.line ?? {};
|
|
247
255
|
const lineRuntime = runtime.channel.line;
|
|
256
|
+
const location = lineData.location;
|
|
257
|
+
const locationMessage = location ? outboundRuntime.createLocationMessage(location) : null;
|
|
258
|
+
const validLocation = locationMessage ? location : void 0;
|
|
248
259
|
const sendText = lineRuntime?.pushMessageLine ?? outboundRuntime.pushMessageLine;
|
|
249
260
|
const sendBatch = lineRuntime?.pushMessagesLine ?? outboundRuntime.pushMessagesLine;
|
|
250
261
|
const sendFlex = lineRuntime?.pushFlexMessage ?? outboundRuntime.pushFlexMessage;
|
|
@@ -256,7 +267,15 @@ const lineOutboundAdapter = {
|
|
|
256
267
|
const recordResult = async (resultPromise) => {
|
|
257
268
|
const result = await resultPromise;
|
|
258
269
|
lastResult = result;
|
|
259
|
-
|
|
270
|
+
try {
|
|
271
|
+
await onDeliveryResult?.(createEmptyChannelResult("line", { ...result }));
|
|
272
|
+
} catch (error) {
|
|
273
|
+
throw createChannelPartialDeliveryError(error, {
|
|
274
|
+
messageIds: listMessageReceiptPlatformIds(result.receipt),
|
|
275
|
+
receipt: result.receipt,
|
|
276
|
+
visibleReplySent: true
|
|
277
|
+
});
|
|
278
|
+
}
|
|
260
279
|
return result;
|
|
261
280
|
};
|
|
262
281
|
const quickReplies = lineData.quickReplies ?? [];
|
|
@@ -278,7 +297,11 @@ const lineOutboundAdapter = {
|
|
|
278
297
|
flexMessages: []
|
|
279
298
|
};
|
|
280
299
|
const chunkLimit = runtime.channel.text.resolveTextChunkLimit?.(cfg, "line", accountId ?? void 0, { fallbackLimit: 5e3 }) ?? 5e3;
|
|
281
|
-
const
|
|
300
|
+
const orderedMessages = processed.segments?.flatMap((segment) => segment.type === "flex" ? [segment.message] : runtime.channel.text.chunkMarkdownText(segment.text, chunkLimit).map((text) => ({
|
|
301
|
+
type: "text",
|
|
302
|
+
text
|
|
303
|
+
})));
|
|
304
|
+
const chunks = orderedMessages ? orderedMessages.flatMap((message) => message.type === "text" ? [message.text] : []) : processed.text ? runtime.channel.text.chunkMarkdownText(processed.text, chunkLimit) : [];
|
|
282
305
|
const mediaUrls = resolveOutboundMediaUrls(payload);
|
|
283
306
|
const useLineSpecificMedia = hasLineSpecificMediaOptions(lineData);
|
|
284
307
|
const mediaOptions = {
|
|
@@ -331,23 +354,42 @@ const lineOutboundAdapter = {
|
|
|
331
354
|
accountId: accountId ?? void 0
|
|
332
355
|
}));
|
|
333
356
|
}
|
|
334
|
-
if (
|
|
357
|
+
if (validLocation) await recordResult(sendLocation(to, validLocation, {
|
|
358
|
+
verbose: false,
|
|
359
|
+
cfg,
|
|
360
|
+
accountId: accountId ?? void 0
|
|
361
|
+
}));
|
|
362
|
+
if (!orderedMessages) for (const flexMsg of processed.flexMessages) await recordResult(sendFlex(to, flexMsg.altText, flexMsg.contents, {
|
|
335
363
|
verbose: false,
|
|
336
364
|
cfg,
|
|
337
365
|
accountId: accountId ?? void 0
|
|
338
366
|
}));
|
|
339
|
-
for (const flexMsg of processed.flexMessages) {
|
|
340
|
-
const flexContents = flexMsg.contents;
|
|
341
|
-
await recordResult(sendFlex(to, flexMsg.altText, flexContents, {
|
|
342
|
-
verbose: false,
|
|
343
|
-
cfg,
|
|
344
|
-
accountId: accountId ?? void 0
|
|
345
|
-
}));
|
|
346
|
-
}
|
|
347
367
|
}
|
|
348
368
|
const sendMediaAfterText = !(hasQuickReplies && chunks.length > 0);
|
|
349
369
|
if (mediaUrls.length > 0 && !shouldSendQuickRepliesInline && !sendMediaAfterText) await sendMediaMessages();
|
|
350
|
-
if (
|
|
370
|
+
if (orderedMessages) for (const [index, message] of orderedMessages.entries()) {
|
|
371
|
+
const isLast = index === orderedMessages.length - 1;
|
|
372
|
+
if (message.type === "flex") if (isLast && quickReply) await sendMessageBatch([{
|
|
373
|
+
...message,
|
|
374
|
+
quickReply
|
|
375
|
+
}]);
|
|
376
|
+
else await recordResult(sendFlex(to, message.altText, message.contents, {
|
|
377
|
+
verbose: false,
|
|
378
|
+
cfg,
|
|
379
|
+
accountId: accountId ?? void 0
|
|
380
|
+
}));
|
|
381
|
+
else if (isLast && hasQuickReplies) await recordResult(sendQuickReplies(to, message.text, quickReplies, {
|
|
382
|
+
verbose: false,
|
|
383
|
+
cfg,
|
|
384
|
+
accountId: accountId ?? void 0
|
|
385
|
+
}));
|
|
386
|
+
else await recordResult(sendText(to, message.text, {
|
|
387
|
+
verbose: false,
|
|
388
|
+
cfg,
|
|
389
|
+
accountId: accountId ?? void 0
|
|
390
|
+
}));
|
|
391
|
+
}
|
|
392
|
+
else if (chunks.length > 0) for (const [i, chunk] of chunks.entries()) if (i === chunks.length - 1 && hasQuickReplies) await recordResult(sendQuickReplies(to, chunk, quickReplies, {
|
|
351
393
|
verbose: false,
|
|
352
394
|
cfg,
|
|
353
395
|
accountId: accountId ?? void 0
|
|
@@ -359,27 +401,13 @@ const lineOutboundAdapter = {
|
|
|
359
401
|
}));
|
|
360
402
|
else if (shouldSendQuickRepliesInline) {
|
|
361
403
|
const quickReplyMessages = [];
|
|
362
|
-
if (lineData.flexMessage) quickReplyMessages.push(
|
|
363
|
-
type: "flex",
|
|
364
|
-
altText: truncateUtf16Safe(lineData.flexMessage.altText, LINE_FLEX_ALT_TEXT_LIMIT),
|
|
365
|
-
contents: lineData.flexMessage.contents
|
|
366
|
-
});
|
|
404
|
+
if (lineData.flexMessage) quickReplyMessages.push(outboundRuntime.createFlexMessage(lineData.flexMessage.altText, lineData.flexMessage.contents));
|
|
367
405
|
if (lineData.templateMessage) {
|
|
368
406
|
const template = buildTemplate(lineData.templateMessage);
|
|
369
407
|
if (template) quickReplyMessages.push(template);
|
|
370
408
|
}
|
|
371
|
-
if (
|
|
372
|
-
|
|
373
|
-
title: truncateUtf16Safe(lineData.location.title, 100),
|
|
374
|
-
address: truncateUtf16Safe(lineData.location.address, 100),
|
|
375
|
-
latitude: lineData.location.latitude,
|
|
376
|
-
longitude: lineData.location.longitude
|
|
377
|
-
});
|
|
378
|
-
for (const flexMsg of processed.flexMessages) quickReplyMessages.push({
|
|
379
|
-
type: "flex",
|
|
380
|
-
altText: truncateUtf16Safe(flexMsg.altText, LINE_FLEX_ALT_TEXT_LIMIT),
|
|
381
|
-
contents: flexMsg.contents
|
|
382
|
-
});
|
|
409
|
+
if (locationMessage) quickReplyMessages.push(locationMessage);
|
|
410
|
+
for (const flexMsg of processed.flexMessages) quickReplyMessages.push(outboundRuntime.createFlexMessage(flexMsg.altText, flexMsg.contents));
|
|
383
411
|
for (const url of mediaUrls) {
|
|
384
412
|
const trimmed = url?.trim();
|
|
385
413
|
if (!trimmed) continue;
|
|
@@ -400,44 +428,15 @@ const lineOutboundAdapter = {
|
|
|
400
428
|
}
|
|
401
429
|
if (mediaUrls.length > 0 && !shouldSendQuickRepliesInline && sendMediaAfterText) await sendMediaMessages();
|
|
402
430
|
const completedResult = lastResult;
|
|
403
|
-
if (completedResult)
|
|
404
|
-
return createEmptyChannelResult("line", {
|
|
405
|
-
messageId: "empty",
|
|
406
|
-
chatId: to
|
|
407
|
-
});
|
|
431
|
+
if (!completedResult) throw new Error("Message must be non-empty for LINE sends");
|
|
432
|
+
return createEmptyChannelResult("line", { ...completedResult });
|
|
408
433
|
},
|
|
409
434
|
...createAttachedChannelResultAdapter({
|
|
410
435
|
channel: "line",
|
|
411
|
-
sendText: async (
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const processed = outboundRuntime.processLineMessage(text);
|
|
416
|
-
let result;
|
|
417
|
-
if (processed.text.trim()) result = await sendText(to, processed.text, {
|
|
418
|
-
verbose: false,
|
|
419
|
-
cfg,
|
|
420
|
-
accountId: accountId ?? void 0
|
|
421
|
-
});
|
|
422
|
-
else result = {
|
|
423
|
-
messageId: "processed",
|
|
424
|
-
chatId: to,
|
|
425
|
-
receipt: createLineSendReceipt({
|
|
426
|
-
messageId: "processed",
|
|
427
|
-
chatId: to,
|
|
428
|
-
kind: "card"
|
|
429
|
-
})
|
|
430
|
-
};
|
|
431
|
-
for (const flexMsg of processed.flexMessages) {
|
|
432
|
-
const flexContents = flexMsg.contents;
|
|
433
|
-
await sendFlex(to, flexMsg.altText, flexContents, {
|
|
434
|
-
verbose: false,
|
|
435
|
-
cfg,
|
|
436
|
-
accountId: accountId ?? void 0
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
return result;
|
|
440
|
-
},
|
|
436
|
+
sendText: async (ctx) => await lineOutboundAdapter.sendPayload({
|
|
437
|
+
...ctx,
|
|
438
|
+
payload: { text: ctx.text }
|
|
439
|
+
}),
|
|
441
440
|
sendMedia: async ({ cfg, to, text, mediaUrl, accountId }) => await (await loadLineOutboundRuntime()).sendMessageLine(to, text, {
|
|
442
441
|
verbose: false,
|
|
443
442
|
mediaUrl,
|
|
@@ -531,7 +530,7 @@ const lineStatusAdapter = createComputedAccountStatusAdapter({
|
|
|
531
530
|
});
|
|
532
531
|
//#endregion
|
|
533
532
|
//#region extensions/line/src/channel.ts
|
|
534
|
-
const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-
|
|
533
|
+
const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-CuKoy9xw.js"));
|
|
535
534
|
const lineSecurityAdapter = createRestrictSendersChannelSecurity({
|
|
536
535
|
channelKey: "line",
|
|
537
536
|
resolveDmPolicy: (account) => account.config.dmPolicy,
|
|
@@ -612,7 +611,6 @@ const linePlugin = createChatChannelPlugin({
|
|
|
612
611
|
}
|
|
613
612
|
},
|
|
614
613
|
directory: createEmptyChannelDirectoryAdapter(),
|
|
615
|
-
setup: lineSetupAdapter,
|
|
616
614
|
setupContract: lineSetupContract,
|
|
617
615
|
status: lineStatusAdapter,
|
|
618
616
|
gateway: lineGatewayAdapter,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as linePlugin } from "./channel-
|
|
1
|
+
import { t as linePlugin } from "./channel-CAdQJRw7.js";
|
|
2
2
|
export { linePlugin };
|
|
@@ -1,5 +1,111 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
2
|
+
import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
|
|
3
|
+
import { resolvePinnedHostnameWithPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
2
4
|
import { isRecord } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
|
|
5
|
+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
6
|
+
//#region extensions/line/src/outbound-media.ts
|
|
7
|
+
const LINE_OUTBOUND_MEDIA_SSRF_POLICY = { allowPrivateNetwork: false };
|
|
8
|
+
async function validateLineMediaUrl(url) {
|
|
9
|
+
let parsed;
|
|
10
|
+
try {
|
|
11
|
+
parsed = new URL(url);
|
|
12
|
+
} catch {
|
|
13
|
+
throw new Error("LINE outbound media URL must be a valid URL");
|
|
14
|
+
}
|
|
15
|
+
if (parsed.protocol !== "https:") throw new Error("LINE outbound media URL must use HTTPS");
|
|
16
|
+
if (url.length > 2e3) throw new Error(`LINE outbound media URL must be 2000 chars or less (got ${url.length})`);
|
|
17
|
+
await resolvePinnedHostnameWithPolicy(parsed.hostname, { policy: LINE_OUTBOUND_MEDIA_SSRF_POLICY });
|
|
18
|
+
}
|
|
19
|
+
function isHttpsUrl(url) {
|
|
20
|
+
try {
|
|
21
|
+
return new URL(url).protocol === "https:";
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function detectLineMediaKindFromUrl(url) {
|
|
27
|
+
try {
|
|
28
|
+
const pathname = normalizeLowercaseStringOrEmpty(new URL(url).pathname);
|
|
29
|
+
if (/\.(png|jpe?g|gif|webp|bmp|heic|heif|avif)$/i.test(pathname)) return "image";
|
|
30
|
+
if (/\.(mp4|mov|m4v|webm)$/i.test(pathname)) return "video";
|
|
31
|
+
if (/\.(mp3|m4a|aac|wav|ogg|oga)$/i.test(pathname)) return "audio";
|
|
32
|
+
} catch {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function resolveLineOutboundMedia(mediaUrl, opts = {}) {
|
|
37
|
+
const trimmedUrl = mediaUrl.trim();
|
|
38
|
+
if (isHttpsUrl(trimmedUrl)) {
|
|
39
|
+
await validateLineMediaUrl(trimmedUrl);
|
|
40
|
+
const previewImageUrl = opts.previewImageUrl?.trim();
|
|
41
|
+
if (previewImageUrl) await validateLineMediaUrl(previewImageUrl);
|
|
42
|
+
return {
|
|
43
|
+
mediaUrl: trimmedUrl,
|
|
44
|
+
mediaKind: opts.mediaKind ?? (typeof opts.durationMs === "number" ? "audio" : void 0) ?? (opts.trackingId?.trim() ? "video" : void 0) ?? detectLineMediaKindFromUrl(trimmedUrl) ?? "image",
|
|
45
|
+
...previewImageUrl ? { previewImageUrl } : {},
|
|
46
|
+
...typeof opts.durationMs === "number" ? { durationMs: opts.durationMs } : {},
|
|
47
|
+
...opts.trackingId ? { trackingId: opts.trackingId } : {}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
let parsed;
|
|
51
|
+
try {
|
|
52
|
+
parsed = new URL(trimmedUrl);
|
|
53
|
+
} catch {}
|
|
54
|
+
if (parsed) throw new Error("LINE outbound media URL must use HTTPS");
|
|
55
|
+
throw new Error("LINE outbound media currently requires a public HTTPS URL");
|
|
56
|
+
}
|
|
57
|
+
function isLineUserTarget(target) {
|
|
58
|
+
const normalized = target.trim().replace(/^line:(group|room|user):/i, "").replace(/^line:/i, "");
|
|
59
|
+
return /^U/i.test(normalized);
|
|
60
|
+
}
|
|
61
|
+
function hasLineSpecificMediaOptions(lineData) {
|
|
62
|
+
return lineData.mediaKind !== void 0 || Boolean(lineData.previewImageUrl?.trim()) || typeof lineData.durationMs === "number" || Boolean(lineData.trackingId?.trim());
|
|
63
|
+
}
|
|
64
|
+
function buildLineMediaMessageObject(resolved, opts) {
|
|
65
|
+
switch (resolved.mediaKind) {
|
|
66
|
+
case "video": {
|
|
67
|
+
const previewImageUrl = resolved.previewImageUrl?.trim();
|
|
68
|
+
if (!previewImageUrl) throw new Error("LINE video messages require previewImageUrl to reference an image URL");
|
|
69
|
+
return {
|
|
70
|
+
type: "video",
|
|
71
|
+
originalContentUrl: resolved.mediaUrl,
|
|
72
|
+
previewImageUrl,
|
|
73
|
+
...opts?.allowTrackingId && resolved.trackingId ? { trackingId: resolved.trackingId } : {}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
case "audio": return {
|
|
77
|
+
type: "audio",
|
|
78
|
+
originalContentUrl: resolved.mediaUrl,
|
|
79
|
+
duration: resolved.durationMs ?? 6e4
|
|
80
|
+
};
|
|
81
|
+
default: return {
|
|
82
|
+
type: "image",
|
|
83
|
+
originalContentUrl: resolved.mediaUrl,
|
|
84
|
+
previewImageUrl: resolved.previewImageUrl ?? resolved.mediaUrl
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function buildLineMediaMessage(mediaUrl, opts, target) {
|
|
89
|
+
return buildLineMediaMessageObject(await resolveLineOutboundMedia(mediaUrl, opts), { allowTrackingId: isLineUserTarget(target) });
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region extensions/line/src/send-receipt.ts
|
|
93
|
+
function createLineSendReceipt(params) {
|
|
94
|
+
const messageIds = (params.messageIds ?? [params.messageId]).map((messageId) => messageId.trim()).filter(Boolean);
|
|
95
|
+
const chatId = params.chatId.trim();
|
|
96
|
+
return createMessageReceiptFromOutboundResults({
|
|
97
|
+
results: messageIds.map((messageId) => ({
|
|
98
|
+
channel: "line",
|
|
99
|
+
messageId,
|
|
100
|
+
chatId,
|
|
101
|
+
conversationId: chatId,
|
|
102
|
+
meta: { messageCount: params.messageCount ?? 1 }
|
|
103
|
+
})),
|
|
104
|
+
...chatId ? { threadId: chatId } : {},
|
|
105
|
+
kind: params.kind ?? "unknown"
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
3
109
|
//#region extensions/line/src/actions.ts
|
|
4
110
|
const LINE_ACTION_LABEL_LIMIT = 20;
|
|
5
111
|
const LINE_ACTION_DATA_LIMIT = 300;
|
|
@@ -546,85 +652,6 @@ function createActionCard(title, body, actions, options) {
|
|
|
546
652
|
};
|
|
547
653
|
return bubble;
|
|
548
654
|
}
|
|
549
|
-
/**
|
|
550
|
-
* Create a carousel container from multiple bubbles
|
|
551
|
-
* LINE allows max 12 bubbles in a carousel
|
|
552
|
-
*/
|
|
553
|
-
function createCarousel(bubbles) {
|
|
554
|
-
return {
|
|
555
|
-
type: "carousel",
|
|
556
|
-
contents: bubbles.slice(0, 12)
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
/**
|
|
560
|
-
* Create a notification bubble (for alerts, status updates)
|
|
561
|
-
*
|
|
562
|
-
* Editorial design: Bold status indicator with accent color,
|
|
563
|
-
* clear typography, optional icon for context.
|
|
564
|
-
*/
|
|
565
|
-
function createNotificationBubble(text, options) {
|
|
566
|
-
const typeColors = {
|
|
567
|
-
info: {
|
|
568
|
-
accent: "#3B82F6",
|
|
569
|
-
bg: "#EFF6FF"
|
|
570
|
-
},
|
|
571
|
-
success: {
|
|
572
|
-
accent: "#06C755",
|
|
573
|
-
bg: "#F0FDF4"
|
|
574
|
-
},
|
|
575
|
-
warning: {
|
|
576
|
-
accent: "#F59E0B",
|
|
577
|
-
bg: "#FFFBEB"
|
|
578
|
-
},
|
|
579
|
-
error: {
|
|
580
|
-
accent: "#EF4444",
|
|
581
|
-
bg: "#FEF2F2"
|
|
582
|
-
}
|
|
583
|
-
}[options?.type ?? "info"];
|
|
584
|
-
const contents = [];
|
|
585
|
-
contents.push({
|
|
586
|
-
type: "box",
|
|
587
|
-
layout: "vertical",
|
|
588
|
-
contents: [],
|
|
589
|
-
width: "4px",
|
|
590
|
-
backgroundColor: typeColors.accent,
|
|
591
|
-
cornerRadius: "2px"
|
|
592
|
-
});
|
|
593
|
-
const textContents = [];
|
|
594
|
-
if (options?.title) textContents.push({
|
|
595
|
-
type: "text",
|
|
596
|
-
text: options.title,
|
|
597
|
-
size: "md",
|
|
598
|
-
weight: "bold",
|
|
599
|
-
color: "#111111",
|
|
600
|
-
wrap: true
|
|
601
|
-
});
|
|
602
|
-
textContents.push({
|
|
603
|
-
type: "text",
|
|
604
|
-
text,
|
|
605
|
-
size: options?.title ? "sm" : "md",
|
|
606
|
-
color: options?.title ? "#666666" : "#333333",
|
|
607
|
-
wrap: true,
|
|
608
|
-
margin: options?.title ? "sm" : void 0
|
|
609
|
-
});
|
|
610
|
-
contents.push({
|
|
611
|
-
type: "box",
|
|
612
|
-
layout: "vertical",
|
|
613
|
-
contents: textContents,
|
|
614
|
-
flex: 1,
|
|
615
|
-
paddingStart: "lg"
|
|
616
|
-
});
|
|
617
|
-
return {
|
|
618
|
-
type: "bubble",
|
|
619
|
-
body: {
|
|
620
|
-
type: "box",
|
|
621
|
-
layout: "horizontal",
|
|
622
|
-
contents,
|
|
623
|
-
paddingAll: "xl",
|
|
624
|
-
backgroundColor: typeColors.bg
|
|
625
|
-
}
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
655
|
//#endregion
|
|
629
656
|
//#region extensions/line/src/flex-templates/schedule-cards.ts
|
|
630
657
|
function buildTitleSubtitleHeader(params) {
|
|
@@ -1336,4 +1363,4 @@ function createDeviceControlCard(params) {
|
|
|
1336
1363
|
return bubble;
|
|
1337
1364
|
}
|
|
1338
1365
|
//#endregion
|
|
1339
|
-
export {
|
|
1366
|
+
export { createLineSendReceipt as _, createEventCard as a, resolveLineOutboundMedia as b, createImageCard as c, datetimePickerAction as d, messageAction as f, uriAction as g, postbackAction as h, createAgendaCard as i, createInfoCard as l, normalizeLineMessageActions as m, createDeviceControlCard as n, createReceiptCard as o, normalizeLineAction as p, createMediaPlayerCard as r, createActionCard as s, createAppleTvRemoteCard as t, createListCard as u, buildLineMediaMessage as v, validateLineMediaUrl as x, hasLineSpecificMediaOptions as y };
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
|
4
4
|
function createLineCardCommandLoader(api) {
|
|
5
5
|
return createLazyRuntimeModule(async () => {
|
|
6
6
|
let registered = null;
|
|
7
|
-
const { registerLineCardCommand } = await import("./card-command-
|
|
7
|
+
const { registerLineCardCommand } = await import("./card-command-DALdUSnc.js");
|
|
8
8
|
registerLineCardCommand({
|
|
9
9
|
...api,
|
|
10
10
|
registerCommand(command) {
|