@openclaw/line 2026.9.6 → 2026.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/icon.png +0 -0
- package/dist/.setup/{accounts-BBEFMYbY.mjs → accounts-DOiM8-y3.mjs} +1 -4
- package/dist/.setup/api-4RgpQmHW.mjs +3 -0
- package/dist/.setup/{api-D_06cuHl.mjs → api-w4QdlCCB.mjs} +2 -2
- package/dist/.setup/{card-command-710u8FBH.mjs → card-command-DYf4gnmI.mjs} +25 -33
- package/dist/.setup/{channel-kc3VRglA.mjs → channel-DN6Za8Vm.mjs} +789 -26
- package/dist/.setup/channel.runtime-bd5vGeSo.mjs +4 -0
- package/dist/.setup/{monitor-morJPcPd.mjs → monitor-C01hAm3f.mjs} +262 -333
- package/dist/.setup/monitor.runtime-DwH2zK48.mjs +2 -0
- package/dist/.setup/outbound.runtime-D_Gs99Dm.mjs +3 -0
- package/dist/.setup/probe.runtime-Cj08-BZ1.mjs +2 -0
- package/dist/.setup/runtime-V7yh9OeC.mjs +8 -0
- package/dist/.setup/{send-BmsHY4WC.mjs → send-DOY1WTxv.mjs} +7 -26
- package/dist/.setup/{send-retry-DbfiHBPd.mjs → send-retry-DV6I0muq.mjs} +104 -208
- package/dist/.setup/{setup-surface-CcfKJKtr.mjs → setup-surface-CbfkmkIS.mjs} +15 -4
- package/dist/.setup/{markdown-to-line-CRfEHFs0.mjs → template-messages-CxCnYsWN.mjs} +168 -189
- package/dist/api.js +2 -2
- package/dist/channel-plugin-api.js +1 -1
- package/dist/contract-api.js +1 -1
- package/dist/index.js +1 -1
- package/dist/runtime-api.js +2 -260
- package/dist/setup-api.js +1 -1
- package/dist/setup-entry.js +1 -1
- package/node_modules/@line/bot-sdk/node_modules/@types/node/README.md +1 -1
- package/node_modules/@line/bot-sdk/node_modules/@types/node/child_process.d.ts +1 -1
- package/node_modules/@line/bot-sdk/node_modules/@types/node/package.json +2 -2
- package/node_modules/typebox/build/compile/code.mjs +1 -2
- package/node_modules/typebox/build/compile/validator.mjs +5 -1
- package/node_modules/typebox/build/format/iri.mjs +16 -9
- package/node_modules/typebox/build/guard/emit.mjs +2 -2
- package/node_modules/typebox/build/guard/guard.d.mts +7 -5
- package/node_modules/typebox/build/guard/guard.mjs +14 -9
- package/node_modules/typebox/build/guard/index.mjs +6 -0
- package/node_modules/typebox/build/guard/unicode/unicode.d.mts +6 -0
- package/node_modules/typebox/build/guard/unicode/unicode.mjs +35 -0
- package/node_modules/typebox/build/guard/{string.mjs → unicode/unicode_segment.mjs} +16 -19
- package/node_modules/typebox/build/schema/build.mjs +2 -3
- package/node_modules/typebox/build/schema/engine/_pathing.d.mts +1 -0
- package/node_modules/typebox/build/schema/engine/_pathing.mjs +11 -0
- package/node_modules/typebox/build/schema/engine/_refine.mjs +13 -5
- package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dependencies.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/format.mjs +9 -2
- package/node_modules/typebox/build/schema/engine/patternProperties.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/properties.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/ref.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/ref.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/schema.mjs +24 -24
- package/node_modules/typebox/build/schema/parse.mjs +3 -0
- package/node_modules/typebox/build/schema/pointer/pointer.d.mts +1 -6
- package/node_modules/typebox/build/schema/pointer/pointer.mjs +47 -64
- package/node_modules/typebox/build/system/settings/internal.d.mts +2 -0
- package/node_modules/typebox/build/system/settings/internal.mjs +20 -0
- package/node_modules/typebox/build/system/settings/settings.d.mts +11 -0
- package/node_modules/typebox/build/system/settings/settings.mjs +14 -0
- package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
- package/node_modules/typebox/build/type/extends/inference.mjs +15 -2
- package/node_modules/typebox/build/type/extends/tuple.d.mts +2 -1
- package/node_modules/typebox/build/type/extends/tuple.mjs +21 -1
- package/node_modules/typebox/build/value/parse/parse.mjs +10 -4
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +3 -1
- package/package.json +5 -5
- package/dist/.setup/account-helpers-BSUyZwLm.mjs +0 -12
- package/dist/.setup/api-RdEuQgW-.mjs +0 -3
- package/dist/.setup/channel.runtime-BtkeDELb.mjs +0 -4
- package/dist/.setup/monitor.runtime-BGhxYDKG.mjs +0 -2
- package/dist/.setup/outbound.runtime-QGvZy8Em.mjs +0 -3
- package/dist/.setup/probe.runtime-CMf8b-jB.mjs +0 -2
- package/dist/.setup/rich-messages-d8CmBVJj.mjs +0 -872
- /package/node_modules/typebox/build/guard/{string.d.mts → unicode/unicode_segment.d.mts} +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { r as lineSetupContract, t as lineSetupWizard } from "./setup-surface-CcfKJKtr.mjs";
|
|
1
|
+
import { n as resolveDefaultLineAccountId, r as resolveLineAccount, t as listLineAccountIds } from "./accounts-DOiM8-y3.mjs";
|
|
2
|
+
import { i as hasLineCredentials, r as lineSetupContract, t as lineSetupWizard } from "./setup-surface-CbfkmkIS.mjs";
|
|
3
|
+
import { t as getLineRuntime } from "./runtime-V7yh9OeC.mjs";
|
|
4
|
+
import { M as inferLineTargetChatType, N as normalizeLineMessagingTarget, O as reportLineQuoteCarrierMissing, S as fitsLineFlexBubble, T as canCarryLineQuoteToken, b as truncateLineActionLabel, c as createAgendaCard, d as createActionCard, g as messageAction, h as createCardBubble, j as buildLineMediaMessage, k as resolveLineQuoteToken, l as createEventCard, s as createLineSendReceipt, t as explainLineRefusal, y as postbackAction } from "./send-retry-DV6I0muq.mjs";
|
|
6
5
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
7
6
|
import { buildDmGroupAccountAllowlistAdapter, createFlatAllowlistOverrideResolver } from "openclaw/plugin-sdk/allowlist-config-edit";
|
|
8
7
|
import { buildChannelOutboundSessionRoute, createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
|
|
@@ -12,7 +11,10 @@ import { createChannelDirectoryAdapter, createResolvedDirectoryEntriesLister } f
|
|
|
12
11
|
import { describeWebhookAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
13
12
|
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
|
14
13
|
import { clearAccountFieldsFromConfigSection, createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
|
15
|
-
import { isRecord, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
14
|
+
import { isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
15
|
+
import { ChannelDeliveryStreamingConfigSchema, DmPolicySchema, GroupPolicySchema, buildChannelConfigSchema, buildGroupEntrySchema, buildMultiAccountChannelSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
|
|
16
|
+
import { requireChannelOpenAllowFrom } from "openclaw/plugin-sdk/extension-shared";
|
|
17
|
+
import { z } from "zod";
|
|
16
18
|
import { parseAccessGroupAllowFromEntry } from "openclaw/plugin-sdk/access-groups";
|
|
17
19
|
import { firstDefined } from "openclaw/plugin-sdk/allow-from";
|
|
18
20
|
import { createAccountStatusSink, defineChannelMessageAdapter, listMessageReceiptPlatformIds } from "openclaw/plugin-sdk/channel-outbound";
|
|
@@ -20,8 +22,10 @@ import { buildTokenChannelStatusSummary, collectIssuesForEnabledAccounts, create
|
|
|
20
22
|
import { createChannelPartialDeliveryError, isChannelPartialDeliveryError } from "openclaw/plugin-sdk/channel-inbound";
|
|
21
23
|
import { createAttachedChannelResultAdapter, createEmptyChannelResult } from "openclaw/plugin-sdk/channel-send-result";
|
|
22
24
|
import { PlatformMessageNotDispatchedError } from "openclaw/plugin-sdk/error-runtime";
|
|
23
|
-
import { resolveOutboundMediaUrls } from "openclaw/plugin-sdk/reply-payload";
|
|
25
|
+
import { resolveAskUserQuestionOptionIndex, resolveAskUserQuestionOptionIndices, resolveOutboundMediaUrls } from "openclaw/plugin-sdk/reply-payload";
|
|
24
26
|
import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
|
|
27
|
+
import { normalizeMessagePresentation, renderMessagePresentationFallbackText, renderPresentationForDelivery, resolveMessagePresentationButtonAction, resolveMessagePresentationOptionAction } from "openclaw/plugin-sdk/interactive-runtime";
|
|
28
|
+
import { Type } from "typebox";
|
|
25
29
|
//#region extensions/line/src/bindings.ts
|
|
26
30
|
function normalizeLineConversationId(raw) {
|
|
27
31
|
const trimmed = raw?.trim() ?? "";
|
|
@@ -49,15 +53,8 @@ const lineBindingsAdapter = {
|
|
|
49
53
|
matchPriority: 2
|
|
50
54
|
};
|
|
51
55
|
},
|
|
52
|
-
resolveCommandConversation:
|
|
53
|
-
|
|
54
|
-
commandTo,
|
|
55
|
-
fallbackTo
|
|
56
|
-
}),
|
|
57
|
-
resolveInboundConversation: ({ to, conversationId }) => resolveLineInboundConversation({
|
|
58
|
-
to,
|
|
59
|
-
conversationId
|
|
60
|
-
})
|
|
56
|
+
resolveCommandConversation: resolveLineCommandConversation,
|
|
57
|
+
resolveInboundConversation: resolveLineInboundConversation
|
|
61
58
|
};
|
|
62
59
|
//#endregion
|
|
63
60
|
//#region extensions/line/src/config-adapter.ts
|
|
@@ -82,6 +79,58 @@ const lineConfigAdapter = createScopedChannelConfigAdapter({
|
|
|
82
79
|
resolveAllowFrom: (account) => account.config.allowFrom,
|
|
83
80
|
formatAllowFrom: (allowFrom) => normalizeStringEntries(allowFrom).map(normalizeLineAllowFrom)
|
|
84
81
|
});
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region extensions/line/src/config-schema.ts
|
|
84
|
+
const ThreadBindingsSchema = z.object({
|
|
85
|
+
enabled: z.boolean().optional(),
|
|
86
|
+
idleHours: z.number().optional(),
|
|
87
|
+
maxAgeHours: z.number().optional(),
|
|
88
|
+
spawnSessions: z.boolean().optional(),
|
|
89
|
+
defaultSpawnContext: z.enum(["isolated", "fork"]).optional()
|
|
90
|
+
}).strict();
|
|
91
|
+
const LineReplyToModeSchema = z.enum([
|
|
92
|
+
"off",
|
|
93
|
+
"first",
|
|
94
|
+
"all"
|
|
95
|
+
]);
|
|
96
|
+
const LineCommonConfigSchemaBase = z.object({
|
|
97
|
+
enabled: z.boolean().optional(),
|
|
98
|
+
configWrites: z.boolean().optional(),
|
|
99
|
+
joinIntro: z.boolean().optional(),
|
|
100
|
+
historyLimit: z.number().int().min(0).optional(),
|
|
101
|
+
channelAccessToken: z.string().optional(),
|
|
102
|
+
channelSecret: z.string().optional(),
|
|
103
|
+
tokenFile: z.string().optional(),
|
|
104
|
+
secretFile: z.string().optional(),
|
|
105
|
+
name: z.string().optional(),
|
|
106
|
+
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
107
|
+
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
108
|
+
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
|
109
|
+
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
|
110
|
+
responsePrefix: z.string().optional(),
|
|
111
|
+
replyToMode: LineReplyToModeSchema.optional(),
|
|
112
|
+
streaming: ChannelDeliveryStreamingConfigSchema.optional(),
|
|
113
|
+
mediaMaxMb: z.number().optional(),
|
|
114
|
+
webhookPath: z.string().optional(),
|
|
115
|
+
threadBindings: ThreadBindingsSchema.optional()
|
|
116
|
+
});
|
|
117
|
+
const LineGroupConfigSchema = buildGroupEntrySchema().omit({
|
|
118
|
+
tools: true,
|
|
119
|
+
toolsBySender: true
|
|
120
|
+
});
|
|
121
|
+
const LineAccountConfigSchema = LineCommonConfigSchemaBase.extend({ groups: z.record(z.string(), LineGroupConfigSchema.optional()).optional() }).strict();
|
|
122
|
+
const LineConfigSchema = buildMultiAccountChannelSchema(LineAccountConfigSchema, {
|
|
123
|
+
optionalAccount: true,
|
|
124
|
+
refine: (value, ctx) => {
|
|
125
|
+
requireChannelOpenAllowFrom({
|
|
126
|
+
channel: "line",
|
|
127
|
+
policy: value.dmPolicy,
|
|
128
|
+
allowFrom: value.allowFrom,
|
|
129
|
+
ctx,
|
|
130
|
+
requireOpenAllowFrom
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
});
|
|
85
134
|
const lineChannelPluginCommon = {
|
|
86
135
|
meta: {
|
|
87
136
|
id: "line",
|
|
@@ -103,7 +152,10 @@ const lineChannelPluginCommon = {
|
|
|
103
152
|
blockStreaming: true
|
|
104
153
|
},
|
|
105
154
|
reload: { configPrefixes: ["channels.line"] },
|
|
106
|
-
configSchema:
|
|
155
|
+
configSchema: buildChannelConfigSchema(LineConfigSchema, { uiHints: { joinIntro: {
|
|
156
|
+
label: "LINE Group Join Introduction",
|
|
157
|
+
help: "Post one brief introduction when the bot joins an allowed LINE group or multi-person room (default: true). Account settings override the channel-wide setting."
|
|
158
|
+
} } }),
|
|
107
159
|
config: {
|
|
108
160
|
...lineConfigAdapter,
|
|
109
161
|
isConfigured: (account) => hasLineCredentials(account),
|
|
@@ -120,10 +172,50 @@ const lineChannelPluginCommon = {
|
|
|
120
172
|
}
|
|
121
173
|
};
|
|
122
174
|
//#endregion
|
|
175
|
+
//#region extensions/line/src/bot-access.ts
|
|
176
|
+
function normalizeLineAllowEntry(value) {
|
|
177
|
+
const trimmed = String(value).trim();
|
|
178
|
+
if (!trimmed) return "";
|
|
179
|
+
if (trimmed === "*") return "*";
|
|
180
|
+
return trimmed.replace(/^line:(?:user:)?/i, "");
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region extensions/line/src/group-keys.ts
|
|
184
|
+
function resolveLineGroupLookupIds(groupId) {
|
|
185
|
+
const normalized = groupId?.trim();
|
|
186
|
+
if (!normalized) return [];
|
|
187
|
+
if (normalized.startsWith("group:") || normalized.startsWith("room:")) {
|
|
188
|
+
const rawId = normalized.split(":").slice(1).join(":");
|
|
189
|
+
return rawId ? [rawId, normalized] : [normalized];
|
|
190
|
+
}
|
|
191
|
+
return [
|
|
192
|
+
normalized,
|
|
193
|
+
`group:${normalized}`,
|
|
194
|
+
`room:${normalized}`
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
function resolveLineGroupConfigEntry(groups, params) {
|
|
198
|
+
if (!groups) return;
|
|
199
|
+
const defaults = groups["*"];
|
|
200
|
+
for (const candidate of [...resolveLineGroupLookupIds(params.groupId), ...resolveLineGroupLookupIds(params.roomId)]) {
|
|
201
|
+
const hit = groups[candidate];
|
|
202
|
+
if (hit) return defaults && defaults !== hit ? {
|
|
203
|
+
...defaults,
|
|
204
|
+
...hit
|
|
205
|
+
} : hit;
|
|
206
|
+
}
|
|
207
|
+
return defaults;
|
|
208
|
+
}
|
|
209
|
+
function resolveExactLineGroupConfigKey(params) {
|
|
210
|
+
const { groups } = params;
|
|
211
|
+
if (!groups) return;
|
|
212
|
+
return resolveLineGroupLookupIds(params.groupId).find((candidate) => Object.hasOwn(groups, candidate));
|
|
213
|
+
}
|
|
214
|
+
//#endregion
|
|
123
215
|
//#region extensions/line/src/doctor.ts
|
|
124
216
|
const GROUP_DEFAULTS_KEY = "*";
|
|
125
217
|
function classifyAllowFrom(values) {
|
|
126
|
-
const entries = Array.isArray(values) ?
|
|
218
|
+
const entries = Array.isArray(values) ? values.map(normalizeLineAllowEntry).filter(Boolean) : [];
|
|
127
219
|
return {
|
|
128
220
|
covered: entries.some((entry) => parseAccessGroupAllowFromEntry(entry) === null),
|
|
129
221
|
referenced: entries.some((entry) => parseAccessGroupAllowFromEntry(entry) !== null)
|
|
@@ -227,7 +319,7 @@ const lineDoctor = {
|
|
|
227
319
|
};
|
|
228
320
|
//#endregion
|
|
229
321
|
//#region extensions/line/src/status.ts
|
|
230
|
-
const loadLineProbeRuntime$1 = createLazyRuntimeModule(() => import("./probe.runtime-
|
|
322
|
+
const loadLineProbeRuntime$1 = createLazyRuntimeModule(() => import("./probe.runtime-Cj08-BZ1.mjs"));
|
|
231
323
|
const collectLineCredentialIssues = createDependentCredentialStatusIssueCollector({
|
|
232
324
|
channel: "line",
|
|
233
325
|
dependencySourceKey: "tokenSource",
|
|
@@ -300,8 +392,8 @@ const lineStatusAdapter = createComputedAccountStatusAdapter({
|
|
|
300
392
|
});
|
|
301
393
|
//#endregion
|
|
302
394
|
//#region extensions/line/src/gateway.ts
|
|
303
|
-
const loadLineProbeRuntime = createLazyRuntimeModule(() => import("./probe.runtime-
|
|
304
|
-
const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-
|
|
395
|
+
const loadLineProbeRuntime = createLazyRuntimeModule(() => import("./probe.runtime-Cj08-BZ1.mjs"));
|
|
396
|
+
const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-DwH2zK48.mjs"));
|
|
305
397
|
const lineGatewayAdapter = {
|
|
306
398
|
startAccount: async (ctx) => {
|
|
307
399
|
const account = ctx.account;
|
|
@@ -380,8 +472,678 @@ function resolveLineGroupRequireMention(params) {
|
|
|
380
472
|
});
|
|
381
473
|
}
|
|
382
474
|
//#endregion
|
|
475
|
+
//#region extensions/line/src/quick-reply-fallback.ts
|
|
476
|
+
function buildLineQuickReplyFallbackText(labels) {
|
|
477
|
+
const normalized = normalizeStringEntries(labels ?? []).slice(0, 13);
|
|
478
|
+
if (normalized.length === 0) return "Choose an option.";
|
|
479
|
+
return `Options:\n${normalized.map((label) => `- ${label}`).join("\n")}`;
|
|
480
|
+
}
|
|
481
|
+
//#endregion
|
|
482
|
+
//#region extensions/line/src/flex-templates/media-control-cards.ts
|
|
483
|
+
function horizontalRow(contents, options = {}) {
|
|
484
|
+
return {
|
|
485
|
+
type: "box",
|
|
486
|
+
layout: "horizontal",
|
|
487
|
+
contents,
|
|
488
|
+
...options
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function createMediaPlayerCard(params) {
|
|
492
|
+
const { title, subtitle, source, imageUrl, isPlaying, progress, controls, extraActions } = params;
|
|
493
|
+
const trackInfo = [{
|
|
494
|
+
type: "text",
|
|
495
|
+
text: title,
|
|
496
|
+
weight: "bold",
|
|
497
|
+
size: "xl",
|
|
498
|
+
color: "#111111",
|
|
499
|
+
wrap: true
|
|
500
|
+
}];
|
|
501
|
+
if (subtitle) trackInfo.push({
|
|
502
|
+
type: "text",
|
|
503
|
+
text: subtitle,
|
|
504
|
+
size: "md",
|
|
505
|
+
color: "#666666",
|
|
506
|
+
wrap: true,
|
|
507
|
+
margin: "sm"
|
|
508
|
+
});
|
|
509
|
+
const statusItems = [];
|
|
510
|
+
if (isPlaying !== void 0) statusItems.push(horizontalRow([{
|
|
511
|
+
type: "box",
|
|
512
|
+
layout: "vertical",
|
|
513
|
+
contents: [],
|
|
514
|
+
width: "8px",
|
|
515
|
+
height: "8px",
|
|
516
|
+
backgroundColor: isPlaying ? "#06C755" : "#CCCCCC",
|
|
517
|
+
cornerRadius: "4px"
|
|
518
|
+
}, {
|
|
519
|
+
type: "text",
|
|
520
|
+
text: isPlaying ? "Now Playing" : "Paused",
|
|
521
|
+
size: "xs",
|
|
522
|
+
color: isPlaying ? "#06C755" : "#888888",
|
|
523
|
+
weight: "bold",
|
|
524
|
+
margin: "sm"
|
|
525
|
+
}], { alignItems: "center" }));
|
|
526
|
+
if (source) statusItems.push({
|
|
527
|
+
type: "text",
|
|
528
|
+
text: source,
|
|
529
|
+
size: "xs",
|
|
530
|
+
color: "#AAAAAA",
|
|
531
|
+
margin: statusItems.length > 0 ? "lg" : void 0
|
|
532
|
+
});
|
|
533
|
+
if (progress) statusItems.push({
|
|
534
|
+
type: "text",
|
|
535
|
+
text: progress,
|
|
536
|
+
size: "xs",
|
|
537
|
+
color: "#888888",
|
|
538
|
+
align: "end",
|
|
539
|
+
flex: 1
|
|
540
|
+
});
|
|
541
|
+
const bodyContents = [{
|
|
542
|
+
type: "box",
|
|
543
|
+
layout: "vertical",
|
|
544
|
+
contents: trackInfo
|
|
545
|
+
}];
|
|
546
|
+
if (statusItems.length > 0) bodyContents.push(horizontalRow(statusItems, {
|
|
547
|
+
margin: "lg",
|
|
548
|
+
alignItems: "center"
|
|
549
|
+
}));
|
|
550
|
+
const bubble = createCardBubble(bodyContents);
|
|
551
|
+
if (imageUrl) bubble.hero = {
|
|
552
|
+
type: "image",
|
|
553
|
+
url: imageUrl,
|
|
554
|
+
size: "full",
|
|
555
|
+
aspectRatio: "1:1",
|
|
556
|
+
aspectMode: "cover"
|
|
557
|
+
};
|
|
558
|
+
if (controls || extraActions?.length) {
|
|
559
|
+
const footerContents = [];
|
|
560
|
+
if (controls) {
|
|
561
|
+
const controlButtons = [];
|
|
562
|
+
for (const [key, label, style] of [
|
|
563
|
+
[
|
|
564
|
+
"previous",
|
|
565
|
+
"⏮",
|
|
566
|
+
"secondary"
|
|
567
|
+
],
|
|
568
|
+
[
|
|
569
|
+
"play",
|
|
570
|
+
"▶",
|
|
571
|
+
isPlaying ? "secondary" : "primary"
|
|
572
|
+
],
|
|
573
|
+
[
|
|
574
|
+
"pause",
|
|
575
|
+
"⏸",
|
|
576
|
+
isPlaying ? "primary" : "secondary"
|
|
577
|
+
],
|
|
578
|
+
[
|
|
579
|
+
"next",
|
|
580
|
+
"⏭",
|
|
581
|
+
"secondary"
|
|
582
|
+
]
|
|
583
|
+
]) {
|
|
584
|
+
const control = controls[key];
|
|
585
|
+
if (!control) continue;
|
|
586
|
+
const button = {
|
|
587
|
+
type: "button",
|
|
588
|
+
action: postbackAction(label, control.data),
|
|
589
|
+
style,
|
|
590
|
+
flex: 1,
|
|
591
|
+
height: "sm"
|
|
592
|
+
};
|
|
593
|
+
if (key !== "previous") button.margin = controlButtons.length > 0 ? "md" : void 0;
|
|
594
|
+
controlButtons.push(button);
|
|
595
|
+
}
|
|
596
|
+
if (controlButtons.length > 0) footerContents.push(horizontalRow(controlButtons));
|
|
597
|
+
}
|
|
598
|
+
if (extraActions?.length) footerContents.push(horizontalRow(extraActions.slice(0, 2).map((action, index) => ({
|
|
599
|
+
type: "button",
|
|
600
|
+
action: postbackAction(truncateLineActionLabel(action.label, 15), action.data),
|
|
601
|
+
style: "secondary",
|
|
602
|
+
flex: 1,
|
|
603
|
+
height: "sm",
|
|
604
|
+
margin: index > 0 ? "md" : void 0
|
|
605
|
+
})), { margin: "md" }));
|
|
606
|
+
if (footerContents.length > 0) bubble.footer = {
|
|
607
|
+
type: "box",
|
|
608
|
+
layout: "vertical",
|
|
609
|
+
contents: footerContents,
|
|
610
|
+
paddingAll: "lg",
|
|
611
|
+
backgroundColor: "#FAFAFA"
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
return bubble;
|
|
615
|
+
}
|
|
616
|
+
function createAppleTvRemoteCard(params) {
|
|
617
|
+
const { deviceName, status, actionData } = params;
|
|
618
|
+
const headerContents = [{
|
|
619
|
+
type: "text",
|
|
620
|
+
text: deviceName,
|
|
621
|
+
weight: "bold",
|
|
622
|
+
size: "xl",
|
|
623
|
+
color: "#111111",
|
|
624
|
+
wrap: true
|
|
625
|
+
}];
|
|
626
|
+
if (status) headerContents.push({
|
|
627
|
+
type: "text",
|
|
628
|
+
text: status,
|
|
629
|
+
size: "sm",
|
|
630
|
+
color: "#666666",
|
|
631
|
+
wrap: true,
|
|
632
|
+
margin: "sm"
|
|
633
|
+
});
|
|
634
|
+
const makeButton = (label, data, style = "secondary") => ({
|
|
635
|
+
type: "button",
|
|
636
|
+
action: postbackAction(label, data),
|
|
637
|
+
style,
|
|
638
|
+
height: "sm",
|
|
639
|
+
flex: 1
|
|
640
|
+
});
|
|
641
|
+
const controlRows = [
|
|
642
|
+
horizontalRow([
|
|
643
|
+
{ type: "filler" },
|
|
644
|
+
makeButton("↑", actionData.up),
|
|
645
|
+
{ type: "filler" }
|
|
646
|
+
]),
|
|
647
|
+
horizontalRow([
|
|
648
|
+
makeButton("←", actionData.left),
|
|
649
|
+
makeButton("OK", actionData.select, "primary"),
|
|
650
|
+
makeButton("→", actionData.right)
|
|
651
|
+
], { margin: "md" }),
|
|
652
|
+
horizontalRow([
|
|
653
|
+
{ type: "filler" },
|
|
654
|
+
makeButton("↓", actionData.down),
|
|
655
|
+
{ type: "filler" }
|
|
656
|
+
], { margin: "md" }),
|
|
657
|
+
horizontalRow([makeButton("Menu", actionData.menu), makeButton("Home", actionData.home)], { margin: "lg" }),
|
|
658
|
+
horizontalRow([makeButton("Play", actionData.play), makeButton("Pause", actionData.pause)], { margin: "md" }),
|
|
659
|
+
horizontalRow([
|
|
660
|
+
makeButton("Vol +", actionData.volumeUp),
|
|
661
|
+
makeButton("Mute", actionData.mute),
|
|
662
|
+
makeButton("Vol -", actionData.volumeDown)
|
|
663
|
+
], { margin: "md" })
|
|
664
|
+
];
|
|
665
|
+
return createCardBubble([
|
|
666
|
+
{
|
|
667
|
+
type: "box",
|
|
668
|
+
layout: "vertical",
|
|
669
|
+
contents: headerContents
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
type: "separator",
|
|
673
|
+
margin: "lg",
|
|
674
|
+
color: "#EEEEEE"
|
|
675
|
+
},
|
|
676
|
+
...controlRows
|
|
677
|
+
]);
|
|
678
|
+
}
|
|
679
|
+
function createDeviceControlCard(params) {
|
|
680
|
+
const { deviceName, deviceType, status, isOnline, imageUrl, controls } = params;
|
|
681
|
+
const headerContents = [horizontalRow([{
|
|
682
|
+
type: "box",
|
|
683
|
+
layout: "vertical",
|
|
684
|
+
contents: [],
|
|
685
|
+
width: "10px",
|
|
686
|
+
height: "10px",
|
|
687
|
+
backgroundColor: isOnline !== false ? "#06C755" : "#FF5555",
|
|
688
|
+
cornerRadius: "5px"
|
|
689
|
+
}, {
|
|
690
|
+
type: "text",
|
|
691
|
+
text: deviceName,
|
|
692
|
+
weight: "bold",
|
|
693
|
+
size: "xl",
|
|
694
|
+
color: "#111111",
|
|
695
|
+
wrap: true,
|
|
696
|
+
flex: 1,
|
|
697
|
+
margin: "md"
|
|
698
|
+
}], { alignItems: "center" })];
|
|
699
|
+
if (deviceType) headerContents.push({
|
|
700
|
+
type: "text",
|
|
701
|
+
text: deviceType,
|
|
702
|
+
size: "sm",
|
|
703
|
+
color: "#888888",
|
|
704
|
+
margin: "sm"
|
|
705
|
+
});
|
|
706
|
+
if (status) headerContents.push({
|
|
707
|
+
type: "box",
|
|
708
|
+
layout: "vertical",
|
|
709
|
+
contents: [{
|
|
710
|
+
type: "text",
|
|
711
|
+
text: status,
|
|
712
|
+
size: "sm",
|
|
713
|
+
color: "#444444",
|
|
714
|
+
wrap: true
|
|
715
|
+
}],
|
|
716
|
+
margin: "lg",
|
|
717
|
+
paddingAll: "md",
|
|
718
|
+
backgroundColor: "#F8F9FA",
|
|
719
|
+
cornerRadius: "md"
|
|
720
|
+
});
|
|
721
|
+
const bubble = createCardBubble(headerContents);
|
|
722
|
+
if (imageUrl) bubble.hero = {
|
|
723
|
+
type: "image",
|
|
724
|
+
url: imageUrl,
|
|
725
|
+
size: "full",
|
|
726
|
+
aspectRatio: "16:9",
|
|
727
|
+
aspectMode: "cover"
|
|
728
|
+
};
|
|
729
|
+
if (controls.length > 0) {
|
|
730
|
+
const rows = [];
|
|
731
|
+
const limitedControls = controls.slice(0, 6);
|
|
732
|
+
for (let i = 0; i < limitedControls.length; i += 2) {
|
|
733
|
+
const rowButtons = [];
|
|
734
|
+
for (const [offset, ctrl] of limitedControls.slice(i, i + 2).entries()) {
|
|
735
|
+
const buttonLabel = ctrl.icon ? `${ctrl.icon} ${ctrl.label}` : ctrl.label;
|
|
736
|
+
rowButtons.push({
|
|
737
|
+
type: "button",
|
|
738
|
+
action: postbackAction(truncateLineActionLabel(buttonLabel, 18), ctrl.data),
|
|
739
|
+
style: ctrl.style ?? "secondary",
|
|
740
|
+
flex: 1,
|
|
741
|
+
height: "sm",
|
|
742
|
+
margin: offset > 0 ? "md" : void 0
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
if (rowButtons.length === 1) rowButtons.push({ type: "filler" });
|
|
746
|
+
rows.push(horizontalRow(rowButtons, { margin: i > 0 ? "md" : void 0 }));
|
|
747
|
+
}
|
|
748
|
+
bubble.footer = {
|
|
749
|
+
type: "box",
|
|
750
|
+
layout: "vertical",
|
|
751
|
+
contents: rows,
|
|
752
|
+
paddingAll: "lg",
|
|
753
|
+
backgroundColor: "#FAFAFA"
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
return bubble;
|
|
757
|
+
}
|
|
758
|
+
//#endregion
|
|
759
|
+
//#region extensions/line/src/question-postback.ts
|
|
760
|
+
const QUESTION_ID_PARAM = "line.question";
|
|
761
|
+
const OPTION_INDEX_PARAM = "line.option";
|
|
762
|
+
const POSTBACK_DATA_MAX_BYTES = 300;
|
|
763
|
+
function withinPostbackLimit(data) {
|
|
764
|
+
return Buffer.byteLength(data, "utf8") <= POSTBACK_DATA_MAX_BYTES ? data : void 0;
|
|
765
|
+
}
|
|
766
|
+
/** Encodes one ask_user choice into LINE postback data, or nothing when it cannot fit. */
|
|
767
|
+
function buildLineQuestionPostbackData(callback) {
|
|
768
|
+
const questionId = normalizeOptionalString(callback.questionId);
|
|
769
|
+
if (!questionId) return;
|
|
770
|
+
const params = new URLSearchParams({ [QUESTION_ID_PARAM]: questionId });
|
|
771
|
+
if (!Number.isInteger(callback.optionIndex) || callback.optionIndex < 0) return;
|
|
772
|
+
params.set(OPTION_INDEX_PARAM, String(callback.optionIndex));
|
|
773
|
+
return withinPostbackLimit(params.toString());
|
|
774
|
+
}
|
|
775
|
+
/** Reads a question choice back out of inbound postback data, if it carries one. */
|
|
776
|
+
function parseLineQuestionPostbackData(data) {
|
|
777
|
+
if (!data.includes(QUESTION_ID_PARAM)) return;
|
|
778
|
+
const params = new URLSearchParams(data);
|
|
779
|
+
const questionId = normalizeOptionalString(params.get(QUESTION_ID_PARAM));
|
|
780
|
+
if (!questionId) return;
|
|
781
|
+
const rawIndex = normalizeOptionalString(params.get(OPTION_INDEX_PARAM));
|
|
782
|
+
if (!rawIndex || !/^\d+$/.test(rawIndex)) return;
|
|
783
|
+
return {
|
|
784
|
+
questionId,
|
|
785
|
+
optionIndex: Number(rawIndex)
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Submit an ask_user choice a LINE tap carried, and report only what the user needs.
|
|
790
|
+
*
|
|
791
|
+
* A successful answer needs no acknowledgement: the agent's own next reply is the
|
|
792
|
+
* feedback, and LINE already echoes the chosen label through the action's
|
|
793
|
+
* `displayText`. Only an answer that cannot land says so.
|
|
794
|
+
*/
|
|
795
|
+
async function resolveLineQuestionPostback(params) {
|
|
796
|
+
try {
|
|
797
|
+
const { questionGatewayRuntime } = await import("openclaw/plugin-sdk/question-gateway-runtime");
|
|
798
|
+
const result = await questionGatewayRuntime.resolveOption({
|
|
799
|
+
cfg: params.cfg,
|
|
800
|
+
questionId: params.callback.questionId,
|
|
801
|
+
senderId: params.senderId,
|
|
802
|
+
clientDisplayName: `LINE question (${params.accountId})`,
|
|
803
|
+
optionIndex: params.callback.optionIndex,
|
|
804
|
+
authorize: params.authorize
|
|
805
|
+
});
|
|
806
|
+
if (result.status === "answered" || result.status === "already-terminal" || result.status === "denied") return { status: result.status };
|
|
807
|
+
return { status: "failed" };
|
|
808
|
+
} catch {
|
|
809
|
+
return { status: "failed" };
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
//#endregion
|
|
813
|
+
//#region extensions/line/src/rich-messages.ts
|
|
814
|
+
const nonempty = () => Type.String({ minLength: 1 });
|
|
815
|
+
const closed = (properties) => Type.Object(properties, { additionalProperties: false });
|
|
816
|
+
const lineCardSchema = Type.Union([
|
|
817
|
+
closed({
|
|
818
|
+
type: Type.Literal("media_player"),
|
|
819
|
+
title: nonempty(),
|
|
820
|
+
artist: Type.Optional(nonempty()),
|
|
821
|
+
source: Type.Optional(nonempty()),
|
|
822
|
+
imageUrl: Type.Optional(Type.String({ pattern: "^https://" })),
|
|
823
|
+
status: Type.Optional(Type.Union([Type.Literal("playing"), Type.Literal("paused")]))
|
|
824
|
+
}),
|
|
825
|
+
closed({
|
|
826
|
+
type: Type.Literal("event"),
|
|
827
|
+
title: nonempty(),
|
|
828
|
+
date: nonempty(),
|
|
829
|
+
time: Type.Optional(nonempty()),
|
|
830
|
+
location: Type.Optional(nonempty()),
|
|
831
|
+
description: Type.Optional(nonempty())
|
|
832
|
+
}),
|
|
833
|
+
closed({
|
|
834
|
+
type: Type.Literal("agenda"),
|
|
835
|
+
title: nonempty(),
|
|
836
|
+
events: Type.Array(closed({
|
|
837
|
+
title: nonempty(),
|
|
838
|
+
time: Type.Optional(nonempty()),
|
|
839
|
+
location: Type.Optional(nonempty())
|
|
840
|
+
}), {
|
|
841
|
+
minItems: 1,
|
|
842
|
+
maxItems: 6
|
|
843
|
+
})
|
|
844
|
+
}),
|
|
845
|
+
closed({
|
|
846
|
+
type: Type.Literal("device"),
|
|
847
|
+
name: nonempty(),
|
|
848
|
+
deviceType: Type.Optional(nonempty()),
|
|
849
|
+
status: Type.Optional(nonempty()),
|
|
850
|
+
controls: Type.Optional(Type.Array(closed({
|
|
851
|
+
label: nonempty(),
|
|
852
|
+
action: nonempty()
|
|
853
|
+
}), { maxItems: 6 }))
|
|
854
|
+
}),
|
|
855
|
+
closed({
|
|
856
|
+
type: Type.Literal("appletv_remote"),
|
|
857
|
+
name: Type.Optional(nonempty()),
|
|
858
|
+
status: Type.Optional(nonempty())
|
|
859
|
+
})
|
|
860
|
+
]);
|
|
861
|
+
const lineChannelDataSchema = Type.Optional(closed({ line: closed({
|
|
862
|
+
location: Type.Optional(closed({
|
|
863
|
+
title: nonempty(),
|
|
864
|
+
address: nonempty(),
|
|
865
|
+
latitude: Type.Number({
|
|
866
|
+
minimum: -90,
|
|
867
|
+
maximum: 90
|
|
868
|
+
}),
|
|
869
|
+
longitude: Type.Number({
|
|
870
|
+
minimum: -180,
|
|
871
|
+
maximum: 180
|
|
872
|
+
})
|
|
873
|
+
})),
|
|
874
|
+
card: Type.Optional(lineCardSchema),
|
|
875
|
+
mediaKind: Type.Optional(Type.Union([
|
|
876
|
+
Type.Literal("image"),
|
|
877
|
+
Type.Literal("video"),
|
|
878
|
+
Type.Literal("audio")
|
|
879
|
+
])),
|
|
880
|
+
previewImageUrl: Type.Optional(Type.String({ pattern: "^https://" })),
|
|
881
|
+
durationMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
882
|
+
trackingId: Type.Optional(nonempty())
|
|
883
|
+
}) }));
|
|
884
|
+
const lineMessageActions = {
|
|
885
|
+
describeMessageTool: ({ cfg, accountId }) => {
|
|
886
|
+
const account = resolveLineAccount({
|
|
887
|
+
cfg,
|
|
888
|
+
accountId: accountId ?? void 0
|
|
889
|
+
});
|
|
890
|
+
return account.enabled && hasLineCredentials(account) ? {
|
|
891
|
+
actions: ["send"],
|
|
892
|
+
capabilities: ["presentation"],
|
|
893
|
+
schema: {
|
|
894
|
+
actions: ["send"],
|
|
895
|
+
properties: { channelData: lineChannelDataSchema }
|
|
896
|
+
}
|
|
897
|
+
} : {
|
|
898
|
+
actions: [],
|
|
899
|
+
capabilities: [],
|
|
900
|
+
schema: null
|
|
901
|
+
};
|
|
902
|
+
},
|
|
903
|
+
prepareSendPayload: ({ payload }) => payload
|
|
904
|
+
};
|
|
905
|
+
const LINE_QUICK_REPLY_LIMIT = 13;
|
|
906
|
+
const LINE_PRESENTATION_CAPABILITIES = {
|
|
907
|
+
supported: true,
|
|
908
|
+
buttons: true,
|
|
909
|
+
selects: true,
|
|
910
|
+
context: true,
|
|
911
|
+
limits: {
|
|
912
|
+
actions: {
|
|
913
|
+
maxActions: 4,
|
|
914
|
+
maxActionsPerRow: 1,
|
|
915
|
+
maxRows: 4,
|
|
916
|
+
maxLabelLength: 40
|
|
917
|
+
},
|
|
918
|
+
selects: {
|
|
919
|
+
maxOptions: LINE_QUICK_REPLY_LIMIT,
|
|
920
|
+
maxValueBytes: 300
|
|
921
|
+
},
|
|
922
|
+
text: { markdownDialect: "plain" }
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
function toLineAction(label, normalized, questionOptionIndices) {
|
|
926
|
+
if (normalized?.type === "question") {
|
|
927
|
+
if ("intent" in normalized) return;
|
|
928
|
+
const optionIndex = resolveAskUserQuestionOptionIndex({
|
|
929
|
+
questionOptionIndices,
|
|
930
|
+
questionId: normalized.questionId,
|
|
931
|
+
optionValue: normalized.optionValue
|
|
932
|
+
});
|
|
933
|
+
const data = optionIndex === void 0 ? void 0 : buildLineQuestionPostbackData({
|
|
934
|
+
questionId: normalized.questionId,
|
|
935
|
+
optionIndex
|
|
936
|
+
});
|
|
937
|
+
if (!data) return;
|
|
938
|
+
return {
|
|
939
|
+
type: "postback",
|
|
940
|
+
label,
|
|
941
|
+
data,
|
|
942
|
+
displayText: label
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
if (normalized?.type === "command") return {
|
|
946
|
+
type: "message",
|
|
947
|
+
label,
|
|
948
|
+
text: normalized.command
|
|
949
|
+
};
|
|
950
|
+
if (normalized?.type === "callback") return {
|
|
951
|
+
type: "postback",
|
|
952
|
+
label,
|
|
953
|
+
data: normalized.value,
|
|
954
|
+
displayText: label
|
|
955
|
+
};
|
|
956
|
+
if (normalized?.type === "url") return {
|
|
957
|
+
type: "uri",
|
|
958
|
+
label,
|
|
959
|
+
uri: normalized.url
|
|
960
|
+
};
|
|
961
|
+
if (normalized?.type === "web-app" && normalized.url) return {
|
|
962
|
+
type: "uri",
|
|
963
|
+
label,
|
|
964
|
+
uri: normalized.url
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
function renderLinePresentation(payload, presentation, to, sourcePresentation = presentation) {
|
|
968
|
+
const hasQuestion = sourcePresentation.blocks.some((block) => block.type === "buttons" && block.buttons.some((button) => resolveMessagePresentationButtonAction(button)?.type === "question"));
|
|
969
|
+
const hasAuthoredPrompt = Boolean(sourcePresentation.title?.trim()) || sourcePresentation.blocks.some((block) => (block.type === "text" || block.type === "context") && block.text.trim());
|
|
970
|
+
if (hasQuestion && !hasAuthoredPrompt) return null;
|
|
971
|
+
if (inferLineTargetChatType(to ?? "") !== "direct" && hasQuestion) return null;
|
|
972
|
+
const hasCard = presentation.blocks.some((block) => block.type === "buttons" && block.buttons.length > 0);
|
|
973
|
+
const buttons = [];
|
|
974
|
+
const quickReplyItems = [];
|
|
975
|
+
const carriedBlocks = [];
|
|
976
|
+
const cardBody = [];
|
|
977
|
+
const omittedControlLabels = [];
|
|
978
|
+
const questionLabels = /* @__PURE__ */ new Set();
|
|
979
|
+
const questionOptionIndices = resolveAskUserQuestionOptionIndices(payload);
|
|
980
|
+
for (const block of presentation.blocks) if (block.type === "buttons") for (const button of block.buttons) {
|
|
981
|
+
const normalized = resolveMessagePresentationButtonAction(button);
|
|
982
|
+
if (normalized?.type === "question" && "intent" in normalized && normalized.intent === "custom-input") {
|
|
983
|
+
omittedControlLabels.push(button.label);
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
const action = toLineAction(button.label, normalized, questionOptionIndices);
|
|
987
|
+
if (!action) return null;
|
|
988
|
+
if (normalized?.type === "question") {
|
|
989
|
+
if (questionLabels.has(button.label)) return null;
|
|
990
|
+
questionLabels.add(button.label);
|
|
991
|
+
}
|
|
992
|
+
buttons.push({
|
|
993
|
+
label: button.label,
|
|
994
|
+
action
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
else if (block.type === "select") {
|
|
998
|
+
const overflow = [];
|
|
999
|
+
for (const option of block.options) {
|
|
1000
|
+
const action = resolveMessagePresentationOptionAction(option);
|
|
1001
|
+
if (!action) return null;
|
|
1002
|
+
if (quickReplyItems.length < LINE_QUICK_REPLY_LIMIT) quickReplyItems.push({
|
|
1003
|
+
label: option.label,
|
|
1004
|
+
action
|
|
1005
|
+
});
|
|
1006
|
+
else overflow.push(option);
|
|
1007
|
+
}
|
|
1008
|
+
if (overflow.length > 0) carriedBlocks.push({
|
|
1009
|
+
...block,
|
|
1010
|
+
options: overflow
|
|
1011
|
+
});
|
|
1012
|
+
else if (block.placeholder) carriedBlocks.push({
|
|
1013
|
+
type: "context",
|
|
1014
|
+
text: block.placeholder
|
|
1015
|
+
});
|
|
1016
|
+
} else if (!hasCard) carriedBlocks.push(block);
|
|
1017
|
+
else if (block.type === "text" || block.type === "context") cardBody.push(block.text);
|
|
1018
|
+
if (buttons.length === 0 && quickReplyItems.length === 0) return null;
|
|
1019
|
+
if (hasCard && omittedControlLabels.length > 0) cardBody.push(`Actions:\n${omittedControlLabels.map((label) => `- ${label}`).join("\n")}`);
|
|
1020
|
+
const lineData = isRecord(payload.channelData?.line) ? payload.channelData.line : {};
|
|
1021
|
+
const title = presentation.title || "Choose an option";
|
|
1022
|
+
const altText = presentation.title || cardBody[0] || title;
|
|
1023
|
+
const flexMessage = hasCard ? {
|
|
1024
|
+
altText,
|
|
1025
|
+
contents: createActionCard(title, cardBody.join("\n") || "Choose an option.", buttons)
|
|
1026
|
+
} : void 0;
|
|
1027
|
+
if (flexMessage && !fitsLineFlexBubble(flexMessage.contents)) return null;
|
|
1028
|
+
const text = renderMessagePresentationFallbackText({
|
|
1029
|
+
text: payload.text,
|
|
1030
|
+
presentation: {
|
|
1031
|
+
title: hasCard ? void 0 : presentation.title,
|
|
1032
|
+
blocks: carriedBlocks
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
return {
|
|
1036
|
+
...payload,
|
|
1037
|
+
...text ? { text } : {},
|
|
1038
|
+
channelData: {
|
|
1039
|
+
...payload.channelData,
|
|
1040
|
+
line: {
|
|
1041
|
+
...lineData,
|
|
1042
|
+
...flexMessage ? { flexMessage } : {},
|
|
1043
|
+
quickReplyItems
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Resolve a reply's portable presentation into LINE-native controls.
|
|
1050
|
+
*
|
|
1051
|
+
* Core runs the presentation renderer inside the outbound send pipeline only, so
|
|
1052
|
+
* replies the plugin delivers itself reach delivery with the controls still
|
|
1053
|
+
* portable. Preparing them here keeps both LINE delivery paths on one rendering.
|
|
1054
|
+
*/
|
|
1055
|
+
async function prepareLineReplyPayload(payload, to) {
|
|
1056
|
+
if (!normalizeMessagePresentation(payload.presentation)) return payload;
|
|
1057
|
+
const usesFallbackText = payload.presentationTextMode === "fallback" && Boolean(payload.text?.trim());
|
|
1058
|
+
return renderPresentationForDelivery({
|
|
1059
|
+
presentationCapabilities: LINE_PRESENTATION_CAPABILITIES,
|
|
1060
|
+
renderPresentation: (adapted, sourcePresentation) => {
|
|
1061
|
+
const rendered = renderLinePresentation(adapted, adapted.presentation, to, sourcePresentation);
|
|
1062
|
+
return rendered && usesFallbackText && rendered.channelData.line.flexMessage === void 0 ? {
|
|
1063
|
+
...rendered,
|
|
1064
|
+
text: payload.text
|
|
1065
|
+
} : rendered;
|
|
1066
|
+
}
|
|
1067
|
+
}, {
|
|
1068
|
+
...payload,
|
|
1069
|
+
presentationTextMode: usesFallbackText ? "fallback" : void 0
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
const toSlug = (value) => normalizeLowercaseStringOrEmpty(value).replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "device";
|
|
1073
|
+
const lineActionData = (action, device) => `line.action=${encodeURIComponent(action)}&line.device=${encodeURIComponent(device)}`;
|
|
1074
|
+
function renderLineCard(card) {
|
|
1075
|
+
if (card.type === "media_player") {
|
|
1076
|
+
const device = toSlug(card.source || card.title);
|
|
1077
|
+
return {
|
|
1078
|
+
altText: `🎵 ${card.title}${card.artist ? ` - ${card.artist}` : ""}`,
|
|
1079
|
+
contents: createMediaPlayerCard({
|
|
1080
|
+
title: card.title,
|
|
1081
|
+
subtitle: card.artist,
|
|
1082
|
+
source: card.source,
|
|
1083
|
+
imageUrl: card.imageUrl,
|
|
1084
|
+
isPlaying: card.status ? card.status === "playing" : void 0,
|
|
1085
|
+
controls: Object.fromEntries([
|
|
1086
|
+
"previous",
|
|
1087
|
+
"play",
|
|
1088
|
+
"pause",
|
|
1089
|
+
"next"
|
|
1090
|
+
].map((action) => [action, { data: lineActionData(action, device) }]))
|
|
1091
|
+
})
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
if (card.type === "event") return {
|
|
1095
|
+
altText: `📅 ${card.title} - ${card.date}${card.time ? ` ${card.time}` : ""}`,
|
|
1096
|
+
contents: createEventCard(card)
|
|
1097
|
+
};
|
|
1098
|
+
if (card.type === "agenda") return {
|
|
1099
|
+
altText: `📋 ${card.title} (${card.events.length} events)`,
|
|
1100
|
+
contents: createAgendaCard(card)
|
|
1101
|
+
};
|
|
1102
|
+
const device = toSlug(card.type === "device" ? card.name : card.name || "apple_tv");
|
|
1103
|
+
if (card.type === "device") return {
|
|
1104
|
+
altText: `📱 ${card.name}${card.status ? `: ${card.status}` : ""}`,
|
|
1105
|
+
contents: createDeviceControlCard({
|
|
1106
|
+
deviceName: card.name,
|
|
1107
|
+
deviceType: card.deviceType,
|
|
1108
|
+
status: card.status,
|
|
1109
|
+
controls: (card.controls ?? []).map((control) => ({
|
|
1110
|
+
label: control.label,
|
|
1111
|
+
data: lineActionData(control.action, device)
|
|
1112
|
+
}))
|
|
1113
|
+
})
|
|
1114
|
+
};
|
|
1115
|
+
const actionData = {
|
|
1116
|
+
up: lineActionData("up", device),
|
|
1117
|
+
down: lineActionData("down", device),
|
|
1118
|
+
left: lineActionData("left", device),
|
|
1119
|
+
right: lineActionData("right", device),
|
|
1120
|
+
select: lineActionData("select", device),
|
|
1121
|
+
menu: lineActionData("menu", device),
|
|
1122
|
+
home: lineActionData("home", device),
|
|
1123
|
+
play: lineActionData("play", device),
|
|
1124
|
+
pause: lineActionData("pause", device),
|
|
1125
|
+
volumeUp: lineActionData("volume_up", device),
|
|
1126
|
+
volumeDown: lineActionData("volume_down", device),
|
|
1127
|
+
mute: lineActionData("mute", device)
|
|
1128
|
+
};
|
|
1129
|
+
return {
|
|
1130
|
+
altText: `📺 ${card.name || "Apple TV"} Remote`,
|
|
1131
|
+
contents: createAppleTvRemoteCard({
|
|
1132
|
+
deviceName: card.name || "Apple TV",
|
|
1133
|
+
status: card.status,
|
|
1134
|
+
actionData
|
|
1135
|
+
})
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
function createLineQuickReply(items) {
|
|
1139
|
+
return { items: items.slice(0, LINE_QUICK_REPLY_LIMIT).map((item) => ({
|
|
1140
|
+
type: "action",
|
|
1141
|
+
action: item.action.type === "command" ? messageAction(item.label, item.action.command) : postbackAction(item.label, item.action.value, item.label)
|
|
1142
|
+
})) };
|
|
1143
|
+
}
|
|
1144
|
+
//#endregion
|
|
383
1145
|
//#region extensions/line/src/outbound.ts
|
|
384
|
-
const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-
|
|
1146
|
+
const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-D_Gs99Dm.mjs"));
|
|
385
1147
|
function quotedOption(quoteToken) {
|
|
386
1148
|
return quoteToken ? { quoteToken } : {};
|
|
387
1149
|
}
|
|
@@ -574,10 +1336,11 @@ const lineOutboundAdapter = {
|
|
|
574
1336
|
if (!trimmed) continue;
|
|
575
1337
|
quickReplyMessages.push(await buildLineMediaMessage(trimmed, mediaOptions, to));
|
|
576
1338
|
}
|
|
577
|
-
|
|
1339
|
+
const lastMessage = quickReplyMessages.at(-1);
|
|
1340
|
+
if (lastMessage && quickReply) {
|
|
578
1341
|
const lastIndex = quickReplyMessages.length - 1;
|
|
579
1342
|
quickReplyMessages[lastIndex] = {
|
|
580
|
-
...
|
|
1343
|
+
...lastMessage,
|
|
581
1344
|
quickReply
|
|
582
1345
|
};
|
|
583
1346
|
await sendMessageBatch(quickReplyMessages);
|
|
@@ -663,7 +1426,7 @@ const lineMessageAdapter = defineChannelMessageAdapter({
|
|
|
663
1426
|
});
|
|
664
1427
|
//#endregion
|
|
665
1428
|
//#region extensions/line/src/channel.ts
|
|
666
|
-
const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-
|
|
1429
|
+
const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-bd5vGeSo.mjs"));
|
|
667
1430
|
const lineSecurityAdapter = createRestrictSendersChannelSecurity({
|
|
668
1431
|
channelKey: "line",
|
|
669
1432
|
resolveDmPolicy: (account) => account.config.dmPolicy,
|
|
@@ -825,4 +1588,4 @@ const linePlugin = createChatChannelPlugin({
|
|
|
825
1588
|
outbound: lineOutboundAdapter
|
|
826
1589
|
});
|
|
827
1590
|
//#endregion
|
|
828
|
-
export { lineChannelPluginCommon as n, linePlugin as t };
|
|
1591
|
+
export { resolveLineQuestionPostback as a, normalizeLineAllowEntry as c, parseLineQuestionPostbackData as i, lineChannelPluginCommon as l, createLineQuickReply as n, buildLineQuickReplyFallbackText as o, prepareLineReplyPayload as r, resolveLineGroupConfigEntry as s, linePlugin as t };
|