@openclaw/line 2026.5.2-beta.2 → 2026.5.3-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.
Files changed (109) hide show
  1. package/dist/accounts-UXAvdBo_.js +105 -0
  2. package/dist/api.js +11 -0
  3. package/dist/basic-cards-7DTG6E-A.js +307 -0
  4. package/dist/card-command-UlPc6Vuc.js +240 -0
  5. package/dist/channel-Rls9s60K.js +601 -0
  6. package/dist/channel-plugin-api.js +2 -0
  7. package/dist/channel.runtime-bXoj8_Sl.js +4 -0
  8. package/dist/contract-api.js +2 -0
  9. package/dist/index.js +45 -0
  10. package/dist/markdown-to-line-CCWK6PJh.js +789 -0
  11. package/dist/monitor-B4_RxHc0.js +1523 -0
  12. package/dist/monitor.runtime-DyxYlrM9.js +2 -0
  13. package/dist/outbound-media-BfhBmWq5.js +55 -0
  14. package/dist/outbound.runtime-B-luYwEB.js +2 -0
  15. package/dist/probe-D9BPzb2n.js +30 -0
  16. package/dist/probe.runtime-N91OWW1J.js +2 -0
  17. package/dist/quick-reply-fallback-HskUUWsn.js +55 -0
  18. package/dist/reply-payload-transform-CFGTs9ln.js +713 -0
  19. package/dist/runtime-api.js +277 -0
  20. package/dist/schedule-cards-BwQChTdM.js +359 -0
  21. package/dist/secret-contract-api.js +5 -0
  22. package/dist/setup-api.js +2 -0
  23. package/dist/setup-entry.js +11 -0
  24. package/dist/setup-surface-DCH7CE-y.js +281 -0
  25. package/package.json +14 -6
  26. package/api.ts +0 -11
  27. package/channel-plugin-api.ts +0 -1
  28. package/contract-api.ts +0 -5
  29. package/index.ts +0 -54
  30. package/runtime-api.ts +0 -187
  31. package/secret-contract-api.ts +0 -4
  32. package/setup-api.ts +0 -2
  33. package/setup-entry.ts +0 -9
  34. package/src/account-helpers.ts +0 -16
  35. package/src/accounts.test.ts +0 -290
  36. package/src/accounts.ts +0 -187
  37. package/src/actions.ts +0 -61
  38. package/src/auto-reply-delivery.test.ts +0 -248
  39. package/src/auto-reply-delivery.ts +0 -200
  40. package/src/bindings.ts +0 -65
  41. package/src/bot-access.ts +0 -48
  42. package/src/bot-handlers.test.ts +0 -1089
  43. package/src/bot-handlers.ts +0 -642
  44. package/src/bot-message-context.test.ts +0 -405
  45. package/src/bot-message-context.ts +0 -581
  46. package/src/bot.ts +0 -70
  47. package/src/card-command.ts +0 -347
  48. package/src/channel-access-token.ts +0 -14
  49. package/src/channel-api.ts +0 -17
  50. package/src/channel-setup-status.contract.test.ts +0 -70
  51. package/src/channel-shared.ts +0 -48
  52. package/src/channel.logout.test.ts +0 -145
  53. package/src/channel.runtime.ts +0 -3
  54. package/src/channel.sendPayload.test.ts +0 -514
  55. package/src/channel.setup.ts +0 -11
  56. package/src/channel.status.test.ts +0 -63
  57. package/src/channel.ts +0 -154
  58. package/src/config-adapter.ts +0 -29
  59. package/src/config-schema.ts +0 -57
  60. package/src/download.test.ts +0 -133
  61. package/src/download.ts +0 -87
  62. package/src/flex-templates/basic-cards.ts +0 -395
  63. package/src/flex-templates/common.ts +0 -20
  64. package/src/flex-templates/media-control-cards.ts +0 -555
  65. package/src/flex-templates/message.ts +0 -13
  66. package/src/flex-templates/schedule-cards.ts +0 -467
  67. package/src/flex-templates/types.ts +0 -22
  68. package/src/flex-templates.ts +0 -32
  69. package/src/gateway.ts +0 -129
  70. package/src/group-keys.test.ts +0 -123
  71. package/src/group-keys.ts +0 -65
  72. package/src/group-policy.ts +0 -22
  73. package/src/markdown-to-line.test.ts +0 -348
  74. package/src/markdown-to-line.ts +0 -416
  75. package/src/message-cards.test.ts +0 -204
  76. package/src/monitor.lifecycle.test.ts +0 -421
  77. package/src/monitor.runtime.ts +0 -1
  78. package/src/monitor.ts +0 -506
  79. package/src/outbound-media.test.ts +0 -189
  80. package/src/outbound-media.ts +0 -120
  81. package/src/outbound.runtime.ts +0 -12
  82. package/src/outbound.ts +0 -356
  83. package/src/probe.contract.test.ts +0 -9
  84. package/src/probe.runtime.ts +0 -1
  85. package/src/probe.ts +0 -34
  86. package/src/quick-reply-fallback.ts +0 -10
  87. package/src/reply-chunks.test.ts +0 -179
  88. package/src/reply-chunks.ts +0 -110
  89. package/src/reply-payload-transform.test.ts +0 -387
  90. package/src/reply-payload-transform.ts +0 -317
  91. package/src/rich-menu.test.ts +0 -310
  92. package/src/rich-menu.ts +0 -326
  93. package/src/runtime.ts +0 -32
  94. package/src/send.test.ts +0 -346
  95. package/src/send.ts +0 -489
  96. package/src/setup-core.ts +0 -149
  97. package/src/setup-runtime-api.ts +0 -9
  98. package/src/setup-surface.test.ts +0 -474
  99. package/src/setup-surface.ts +0 -227
  100. package/src/signature.test.ts +0 -34
  101. package/src/signature.ts +0 -24
  102. package/src/status.ts +0 -37
  103. package/src/template-messages.ts +0 -333
  104. package/src/types.ts +0 -128
  105. package/src/webhook-node.test.ts +0 -513
  106. package/src/webhook-node.ts +0 -131
  107. package/src/webhook-utils.ts +0 -10
  108. package/src/webhook.ts +0 -111
  109. package/tsconfig.json +0 -16
@@ -0,0 +1,713 @@
1
+ import { n as createEventCard, t as createAgendaCard } from "./schedule-cards-BwQChTdM.js";
2
+ import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
3
+ import { z } from "openclaw/plugin-sdk/zod";
4
+ import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
5
+ //#region extensions/line/src/config-schema.ts
6
+ const DmPolicySchema = z.enum([
7
+ "open",
8
+ "allowlist",
9
+ "pairing",
10
+ "disabled"
11
+ ]);
12
+ const GroupPolicySchema = z.enum([
13
+ "open",
14
+ "allowlist",
15
+ "disabled"
16
+ ]);
17
+ const ThreadBindingsSchema = z.object({
18
+ enabled: z.boolean().optional(),
19
+ idleHours: z.number().optional(),
20
+ maxAgeHours: z.number().optional(),
21
+ spawnSessions: z.boolean().optional(),
22
+ defaultSpawnContext: z.enum(["isolated", "fork"]).optional(),
23
+ spawnSubagentSessions: z.boolean().optional(),
24
+ spawnAcpSessions: z.boolean().optional()
25
+ }).strict();
26
+ const LineCommonConfigSchema = z.object({
27
+ enabled: z.boolean().optional(),
28
+ channelAccessToken: z.string().optional(),
29
+ channelSecret: z.string().optional(),
30
+ tokenFile: z.string().optional(),
31
+ secretFile: z.string().optional(),
32
+ name: z.string().optional(),
33
+ allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
34
+ groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
35
+ dmPolicy: DmPolicySchema.optional().default("pairing"),
36
+ groupPolicy: GroupPolicySchema.optional().default("allowlist"),
37
+ responsePrefix: z.string().optional(),
38
+ mediaMaxMb: z.number().optional(),
39
+ webhookPath: z.string().optional(),
40
+ threadBindings: ThreadBindingsSchema.optional()
41
+ });
42
+ const LineGroupConfigSchema = z.object({
43
+ enabled: z.boolean().optional(),
44
+ allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
45
+ requireMention: z.boolean().optional(),
46
+ systemPrompt: z.string().optional(),
47
+ skills: z.array(z.string()).optional()
48
+ }).strict();
49
+ const LineAccountConfigSchema = LineCommonConfigSchema.extend({ groups: z.record(z.string(), LineGroupConfigSchema.optional()).optional() }).strict();
50
+ const LineConfigSchema = LineCommonConfigSchema.extend({
51
+ accounts: z.record(z.string(), LineAccountConfigSchema.optional()).optional(),
52
+ defaultAccount: z.string().optional(),
53
+ groups: z.record(z.string(), LineGroupConfigSchema.optional()).optional()
54
+ }).strict();
55
+ const LineChannelConfigSchema = buildChannelConfigSchema(LineConfigSchema);
56
+ //#endregion
57
+ //#region extensions/line/src/flex-templates/media-control-cards.ts
58
+ /**
59
+ * Create a media player card for Sonos, Spotify, Apple Music, etc.
60
+ *
61
+ * Editorial design: Album art hero with gradient overlay for text,
62
+ * prominent now-playing indicator, refined playback controls.
63
+ */
64
+ function createMediaPlayerCard(params) {
65
+ const { title, subtitle, source, imageUrl, isPlaying, progress, controls, extraActions } = params;
66
+ const trackInfo = [{
67
+ type: "text",
68
+ text: title,
69
+ weight: "bold",
70
+ size: "xl",
71
+ color: "#111111",
72
+ wrap: true
73
+ }];
74
+ if (subtitle) trackInfo.push({
75
+ type: "text",
76
+ text: subtitle,
77
+ size: "md",
78
+ color: "#666666",
79
+ wrap: true,
80
+ margin: "sm"
81
+ });
82
+ const statusItems = [];
83
+ if (isPlaying !== void 0) statusItems.push({
84
+ type: "box",
85
+ layout: "horizontal",
86
+ contents: [{
87
+ type: "box",
88
+ layout: "vertical",
89
+ contents: [],
90
+ width: "8px",
91
+ height: "8px",
92
+ backgroundColor: isPlaying ? "#06C755" : "#CCCCCC",
93
+ cornerRadius: "4px"
94
+ }, {
95
+ type: "text",
96
+ text: isPlaying ? "Now Playing" : "Paused",
97
+ size: "xs",
98
+ color: isPlaying ? "#06C755" : "#888888",
99
+ weight: "bold",
100
+ margin: "sm"
101
+ }],
102
+ alignItems: "center"
103
+ });
104
+ if (source) statusItems.push({
105
+ type: "text",
106
+ text: source,
107
+ size: "xs",
108
+ color: "#AAAAAA",
109
+ margin: statusItems.length > 0 ? "lg" : void 0
110
+ });
111
+ if (progress) statusItems.push({
112
+ type: "text",
113
+ text: progress,
114
+ size: "xs",
115
+ color: "#888888",
116
+ align: "end",
117
+ flex: 1
118
+ });
119
+ const bodyContents = [{
120
+ type: "box",
121
+ layout: "vertical",
122
+ contents: trackInfo
123
+ }];
124
+ if (statusItems.length > 0) bodyContents.push({
125
+ type: "box",
126
+ layout: "horizontal",
127
+ contents: statusItems,
128
+ margin: "lg",
129
+ alignItems: "center"
130
+ });
131
+ const bubble = {
132
+ type: "bubble",
133
+ size: "mega",
134
+ body: {
135
+ type: "box",
136
+ layout: "vertical",
137
+ contents: bodyContents,
138
+ paddingAll: "xl",
139
+ backgroundColor: "#FFFFFF"
140
+ }
141
+ };
142
+ if (imageUrl) bubble.hero = {
143
+ type: "image",
144
+ url: imageUrl,
145
+ size: "full",
146
+ aspectRatio: "1:1",
147
+ aspectMode: "cover"
148
+ };
149
+ if (controls || extraActions?.length) {
150
+ const footerContents = [];
151
+ if (controls) {
152
+ const controlButtons = [];
153
+ if (controls.previous) controlButtons.push({
154
+ type: "button",
155
+ action: {
156
+ type: "postback",
157
+ label: "⏮",
158
+ data: controls.previous.data
159
+ },
160
+ style: "secondary",
161
+ flex: 1,
162
+ height: "sm"
163
+ });
164
+ if (controls.play) controlButtons.push({
165
+ type: "button",
166
+ action: {
167
+ type: "postback",
168
+ label: "▶",
169
+ data: controls.play.data
170
+ },
171
+ style: isPlaying ? "secondary" : "primary",
172
+ flex: 1,
173
+ height: "sm",
174
+ margin: controls.previous ? "md" : void 0
175
+ });
176
+ if (controls.pause) controlButtons.push({
177
+ type: "button",
178
+ action: {
179
+ type: "postback",
180
+ label: "⏸",
181
+ data: controls.pause.data
182
+ },
183
+ style: isPlaying ? "primary" : "secondary",
184
+ flex: 1,
185
+ height: "sm",
186
+ margin: controlButtons.length > 0 ? "md" : void 0
187
+ });
188
+ if (controls.next) controlButtons.push({
189
+ type: "button",
190
+ action: {
191
+ type: "postback",
192
+ label: "⏭",
193
+ data: controls.next.data
194
+ },
195
+ style: "secondary",
196
+ flex: 1,
197
+ height: "sm",
198
+ margin: controlButtons.length > 0 ? "md" : void 0
199
+ });
200
+ if (controlButtons.length > 0) footerContents.push({
201
+ type: "box",
202
+ layout: "horizontal",
203
+ contents: controlButtons
204
+ });
205
+ }
206
+ if (extraActions?.length) footerContents.push({
207
+ type: "box",
208
+ layout: "horizontal",
209
+ contents: extraActions.slice(0, 2).map((action, index) => ({
210
+ type: "button",
211
+ action: {
212
+ type: "postback",
213
+ label: action.label.slice(0, 15),
214
+ data: action.data
215
+ },
216
+ style: "secondary",
217
+ flex: 1,
218
+ height: "sm",
219
+ margin: index > 0 ? "md" : void 0
220
+ })),
221
+ margin: "md"
222
+ });
223
+ if (footerContents.length > 0) bubble.footer = {
224
+ type: "box",
225
+ layout: "vertical",
226
+ contents: footerContents,
227
+ paddingAll: "lg",
228
+ backgroundColor: "#FAFAFA"
229
+ };
230
+ }
231
+ return bubble;
232
+ }
233
+ /**
234
+ * Create an Apple TV remote card with a D-pad and control rows.
235
+ */
236
+ function createAppleTvRemoteCard(params) {
237
+ const { deviceName, status, actionData } = params;
238
+ const headerContents = [{
239
+ type: "text",
240
+ text: deviceName,
241
+ weight: "bold",
242
+ size: "xl",
243
+ color: "#111111",
244
+ wrap: true
245
+ }];
246
+ if (status) headerContents.push({
247
+ type: "text",
248
+ text: status,
249
+ size: "sm",
250
+ color: "#666666",
251
+ wrap: true,
252
+ margin: "sm"
253
+ });
254
+ const makeButton = (label, data, style = "secondary") => ({
255
+ type: "button",
256
+ action: {
257
+ type: "postback",
258
+ label,
259
+ data
260
+ },
261
+ style,
262
+ height: "sm",
263
+ flex: 1
264
+ });
265
+ const dpadRows = [
266
+ {
267
+ type: "box",
268
+ layout: "horizontal",
269
+ contents: [
270
+ { type: "filler" },
271
+ makeButton("↑", actionData.up),
272
+ { type: "filler" }
273
+ ]
274
+ },
275
+ {
276
+ type: "box",
277
+ layout: "horizontal",
278
+ contents: [
279
+ makeButton("←", actionData.left),
280
+ makeButton("OK", actionData.select, "primary"),
281
+ makeButton("→", actionData.right)
282
+ ],
283
+ margin: "md"
284
+ },
285
+ {
286
+ type: "box",
287
+ layout: "horizontal",
288
+ contents: [
289
+ { type: "filler" },
290
+ makeButton("↓", actionData.down),
291
+ { type: "filler" }
292
+ ],
293
+ margin: "md"
294
+ }
295
+ ];
296
+ const menuRow = {
297
+ type: "box",
298
+ layout: "horizontal",
299
+ contents: [makeButton("Menu", actionData.menu), makeButton("Home", actionData.home)],
300
+ margin: "lg"
301
+ };
302
+ const playbackRow = {
303
+ type: "box",
304
+ layout: "horizontal",
305
+ contents: [makeButton("Play", actionData.play), makeButton("Pause", actionData.pause)],
306
+ margin: "md"
307
+ };
308
+ const volumeRow = {
309
+ type: "box",
310
+ layout: "horizontal",
311
+ contents: [
312
+ makeButton("Vol +", actionData.volumeUp),
313
+ makeButton("Mute", actionData.mute),
314
+ makeButton("Vol -", actionData.volumeDown)
315
+ ],
316
+ margin: "md"
317
+ };
318
+ return {
319
+ type: "bubble",
320
+ size: "mega",
321
+ body: {
322
+ type: "box",
323
+ layout: "vertical",
324
+ contents: [
325
+ {
326
+ type: "box",
327
+ layout: "vertical",
328
+ contents: headerContents
329
+ },
330
+ {
331
+ type: "separator",
332
+ margin: "lg",
333
+ color: "#EEEEEE"
334
+ },
335
+ ...dpadRows,
336
+ menuRow,
337
+ playbackRow,
338
+ volumeRow
339
+ ],
340
+ paddingAll: "xl",
341
+ backgroundColor: "#FFFFFF"
342
+ }
343
+ };
344
+ }
345
+ /**
346
+ * Create a device control card for Apple TV, smart home devices, etc.
347
+ *
348
+ * Editorial design: Device-focused header with status indicator,
349
+ * clean control grid with clear visual hierarchy.
350
+ */
351
+ function createDeviceControlCard(params) {
352
+ const { deviceName, deviceType, status, isOnline, imageUrl, controls } = params;
353
+ const headerContents = [{
354
+ type: "box",
355
+ layout: "horizontal",
356
+ contents: [{
357
+ type: "box",
358
+ layout: "vertical",
359
+ contents: [],
360
+ width: "10px",
361
+ height: "10px",
362
+ backgroundColor: isOnline !== false ? "#06C755" : "#FF5555",
363
+ cornerRadius: "5px"
364
+ }, {
365
+ type: "text",
366
+ text: deviceName,
367
+ weight: "bold",
368
+ size: "xl",
369
+ color: "#111111",
370
+ wrap: true,
371
+ flex: 1,
372
+ margin: "md"
373
+ }],
374
+ alignItems: "center"
375
+ }];
376
+ if (deviceType) headerContents.push({
377
+ type: "text",
378
+ text: deviceType,
379
+ size: "sm",
380
+ color: "#888888",
381
+ margin: "sm"
382
+ });
383
+ if (status) headerContents.push({
384
+ type: "box",
385
+ layout: "vertical",
386
+ contents: [{
387
+ type: "text",
388
+ text: status,
389
+ size: "sm",
390
+ color: "#444444",
391
+ wrap: true
392
+ }],
393
+ margin: "lg",
394
+ paddingAll: "md",
395
+ backgroundColor: "#F8F9FA",
396
+ cornerRadius: "md"
397
+ });
398
+ const bubble = {
399
+ type: "bubble",
400
+ size: "mega",
401
+ body: {
402
+ type: "box",
403
+ layout: "vertical",
404
+ contents: headerContents,
405
+ paddingAll: "xl",
406
+ backgroundColor: "#FFFFFF"
407
+ }
408
+ };
409
+ if (imageUrl) bubble.hero = {
410
+ type: "image",
411
+ url: imageUrl,
412
+ size: "full",
413
+ aspectRatio: "16:9",
414
+ aspectMode: "cover"
415
+ };
416
+ if (controls.length > 0) {
417
+ const rows = [];
418
+ const limitedControls = controls.slice(0, 6);
419
+ for (let i = 0; i < limitedControls.length; i += 2) {
420
+ const rowButtons = [];
421
+ for (let j = i; j < Math.min(i + 2, limitedControls.length); j++) {
422
+ const ctrl = limitedControls[j];
423
+ const buttonLabel = ctrl.icon ? `${ctrl.icon} ${ctrl.label}` : ctrl.label;
424
+ rowButtons.push({
425
+ type: "button",
426
+ action: {
427
+ type: "postback",
428
+ label: buttonLabel.slice(0, 18),
429
+ data: ctrl.data
430
+ },
431
+ style: ctrl.style ?? "secondary",
432
+ flex: 1,
433
+ height: "sm",
434
+ margin: j > i ? "md" : void 0
435
+ });
436
+ }
437
+ if (rowButtons.length === 1) rowButtons.push({ type: "filler" });
438
+ rows.push({
439
+ type: "box",
440
+ layout: "horizontal",
441
+ contents: rowButtons,
442
+ margin: i > 0 ? "md" : void 0
443
+ });
444
+ }
445
+ bubble.footer = {
446
+ type: "box",
447
+ layout: "vertical",
448
+ contents: rows,
449
+ paddingAll: "lg",
450
+ backgroundColor: "#FAFAFA"
451
+ };
452
+ }
453
+ return bubble;
454
+ }
455
+ //#endregion
456
+ //#region extensions/line/src/reply-payload-transform.ts
457
+ /**
458
+ * Parse LINE-specific directives from text and extract them into ReplyPayload fields.
459
+ *
460
+ * Supported directives:
461
+ * - [[quick_replies: option1, option2, option3]]
462
+ * - [[location: title | address | latitude | longitude]]
463
+ * - [[confirm: question | yes_label | no_label]]
464
+ * - [[buttons: title | text | btn1:data1, btn2:data2]]
465
+ * - [[media_player: title | artist | source | imageUrl | playing/paused]]
466
+ * - [[event: title | date | time | location | description]]
467
+ * - [[agenda: title | event1_title:event1_time, event2_title:event2_time, ...]]
468
+ * - [[device: name | type | status | ctrl1:data1, ctrl2:data2]]
469
+ * - [[appletv_remote: name | status]]
470
+ */
471
+ function parseLineDirectives(payload) {
472
+ let text = payload.text;
473
+ if (!text) return payload;
474
+ const result = { ...payload };
475
+ const lineData = { ...result.channelData?.line };
476
+ const toSlug = (value) => normalizeLowercaseStringOrEmpty(value).replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "device";
477
+ const lineActionData = (action, extras) => {
478
+ const base = [`line.action=${encodeURIComponent(action)}`];
479
+ if (extras) for (const [key, value] of Object.entries(extras)) base.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
480
+ return base.join("&");
481
+ };
482
+ const quickRepliesMatch = text.match(/\[\[quick_replies:\s*([^\]]+)\]\]/i);
483
+ if (quickRepliesMatch) {
484
+ const options = quickRepliesMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
485
+ if (options.length > 0) lineData.quickReplies = [...lineData.quickReplies || [], ...options];
486
+ text = text.replace(quickRepliesMatch[0], "").trim();
487
+ }
488
+ const locationMatch = text.match(/\[\[location:\s*([^\]]+)\]\]/i);
489
+ if (locationMatch && !lineData.location) {
490
+ const parts = locationMatch[1].split("|").map((s) => s.trim());
491
+ if (parts.length >= 4) {
492
+ const [title, address, latStr, lonStr] = parts;
493
+ const latitude = Number.parseFloat(latStr);
494
+ const longitude = Number.parseFloat(lonStr);
495
+ if (!Number.isNaN(latitude) && !Number.isNaN(longitude)) lineData.location = {
496
+ title: title || "Location",
497
+ address: address || "",
498
+ latitude,
499
+ longitude
500
+ };
501
+ }
502
+ text = text.replace(locationMatch[0], "").trim();
503
+ }
504
+ const confirmMatch = text.match(/\[\[confirm:\s*([^\]]+)\]\]/i);
505
+ if (confirmMatch && !lineData.templateMessage) {
506
+ const parts = confirmMatch[1].split("|").map((s) => s.trim());
507
+ if (parts.length >= 3) {
508
+ const [question, yesPart, noPart] = parts;
509
+ const [yesLabel, yesData] = yesPart.includes(":") ? yesPart.split(":").map((s) => s.trim()) : [yesPart, normalizeLowercaseStringOrEmpty(yesPart)];
510
+ const [noLabel, noData] = noPart.includes(":") ? noPart.split(":").map((s) => s.trim()) : [noPart, normalizeLowercaseStringOrEmpty(noPart)];
511
+ lineData.templateMessage = {
512
+ type: "confirm",
513
+ text: question,
514
+ confirmLabel: yesLabel,
515
+ confirmData: yesData,
516
+ cancelLabel: noLabel,
517
+ cancelData: noData,
518
+ altText: question
519
+ };
520
+ }
521
+ text = text.replace(confirmMatch[0], "").trim();
522
+ }
523
+ const buttonsMatch = text.match(/\[\[buttons:\s*([^\]]+)\]\]/i);
524
+ if (buttonsMatch && !lineData.templateMessage) {
525
+ const parts = buttonsMatch[1].split("|").map((s) => s.trim());
526
+ if (parts.length >= 3) {
527
+ const [title, bodyText, actionsStr] = parts;
528
+ const actions = actionsStr.split(",").map((actionStr) => {
529
+ const trimmed = actionStr.trim();
530
+ const colonIndex = (() => {
531
+ const index = trimmed.indexOf(":");
532
+ if (index === -1) return -1;
533
+ const lower = normalizeLowercaseStringOrEmpty(trimmed);
534
+ if (lower.startsWith("http://") || lower.startsWith("https://")) return -1;
535
+ return index;
536
+ })();
537
+ let label;
538
+ let data;
539
+ if (colonIndex === -1) {
540
+ label = trimmed;
541
+ data = trimmed;
542
+ } else {
543
+ label = trimmed.slice(0, colonIndex).trim();
544
+ data = trimmed.slice(colonIndex + 1).trim();
545
+ }
546
+ if (data.startsWith("http://") || data.startsWith("https://")) return {
547
+ type: "uri",
548
+ label,
549
+ uri: data
550
+ };
551
+ if (data.includes("=")) return {
552
+ type: "postback",
553
+ label,
554
+ data
555
+ };
556
+ return {
557
+ type: "message",
558
+ label,
559
+ data: data || label
560
+ };
561
+ });
562
+ if (actions.length > 0) lineData.templateMessage = {
563
+ type: "buttons",
564
+ title,
565
+ text: bodyText,
566
+ actions: actions.slice(0, 4),
567
+ altText: `${title}: ${bodyText}`
568
+ };
569
+ }
570
+ text = text.replace(buttonsMatch[0], "").trim();
571
+ }
572
+ const mediaPlayerMatch = text.match(/\[\[media_player:\s*([^\]]+)\]\]/i);
573
+ if (mediaPlayerMatch && !lineData.flexMessage) {
574
+ const parts = mediaPlayerMatch[1].split("|").map((s) => s.trim());
575
+ if (parts.length >= 1) {
576
+ const [title, artist, source, imageUrl, statusStr] = parts;
577
+ const isPlaying = normalizeLowercaseStringOrEmpty(statusStr) === "playing";
578
+ const validImageUrl = imageUrl?.startsWith("https://") ? imageUrl : void 0;
579
+ const deviceKey = toSlug(source || title || "media");
580
+ const card = createMediaPlayerCard({
581
+ title: title || "Unknown Track",
582
+ subtitle: artist || void 0,
583
+ source: source || void 0,
584
+ imageUrl: validImageUrl,
585
+ isPlaying: statusStr ? isPlaying : void 0,
586
+ controls: {
587
+ previous: { data: lineActionData("previous", { "line.device": deviceKey }) },
588
+ play: { data: lineActionData("play", { "line.device": deviceKey }) },
589
+ pause: { data: lineActionData("pause", { "line.device": deviceKey }) },
590
+ next: { data: lineActionData("next", { "line.device": deviceKey }) }
591
+ }
592
+ });
593
+ lineData.flexMessage = {
594
+ altText: `🎵 ${title}${artist ? ` - ${artist}` : ""}`,
595
+ contents: card
596
+ };
597
+ }
598
+ text = text.replace(mediaPlayerMatch[0], "").trim();
599
+ }
600
+ const eventMatch = text.match(/\[\[event:\s*([^\]]+)\]\]/i);
601
+ if (eventMatch && !lineData.flexMessage) {
602
+ const parts = eventMatch[1].split("|").map((s) => s.trim());
603
+ if (parts.length >= 2) {
604
+ const [title, date, time, location, description] = parts;
605
+ const card = createEventCard({
606
+ title: title || "Event",
607
+ date: date || "TBD",
608
+ time: time || void 0,
609
+ location: location || void 0,
610
+ description: description || void 0
611
+ });
612
+ lineData.flexMessage = {
613
+ altText: `📅 ${title} - ${date}${time ? ` ${time}` : ""}`,
614
+ contents: card
615
+ };
616
+ }
617
+ text = text.replace(eventMatch[0], "").trim();
618
+ }
619
+ const appleTvMatch = text.match(/\[\[appletv_remote:\s*([^\]]+)\]\]/i);
620
+ if (appleTvMatch && !lineData.flexMessage) {
621
+ const parts = appleTvMatch[1].split("|").map((s) => s.trim());
622
+ if (parts.length >= 1) {
623
+ const [deviceName, status] = parts;
624
+ const deviceKey = toSlug(deviceName || "apple_tv");
625
+ const card = createAppleTvRemoteCard({
626
+ deviceName: deviceName || "Apple TV",
627
+ status: status || void 0,
628
+ actionData: {
629
+ up: lineActionData("up", { "line.device": deviceKey }),
630
+ down: lineActionData("down", { "line.device": deviceKey }),
631
+ left: lineActionData("left", { "line.device": deviceKey }),
632
+ right: lineActionData("right", { "line.device": deviceKey }),
633
+ select: lineActionData("select", { "line.device": deviceKey }),
634
+ menu: lineActionData("menu", { "line.device": deviceKey }),
635
+ home: lineActionData("home", { "line.device": deviceKey }),
636
+ play: lineActionData("play", { "line.device": deviceKey }),
637
+ pause: lineActionData("pause", { "line.device": deviceKey }),
638
+ volumeUp: lineActionData("volume_up", { "line.device": deviceKey }),
639
+ volumeDown: lineActionData("volume_down", { "line.device": deviceKey }),
640
+ mute: lineActionData("mute", { "line.device": deviceKey })
641
+ }
642
+ });
643
+ lineData.flexMessage = {
644
+ altText: `📺 ${deviceName || "Apple TV"} Remote`,
645
+ contents: card
646
+ };
647
+ }
648
+ text = text.replace(appleTvMatch[0], "").trim();
649
+ }
650
+ const agendaMatch = text.match(/\[\[agenda:\s*([^\]]+)\]\]/i);
651
+ if (agendaMatch && !lineData.flexMessage) {
652
+ const parts = agendaMatch[1].split("|").map((s) => s.trim());
653
+ if (parts.length >= 2) {
654
+ const [title, eventsStr] = parts;
655
+ const events = eventsStr.split(",").map((eventStr) => {
656
+ const trimmed = eventStr.trim();
657
+ const colonIdx = trimmed.lastIndexOf(":");
658
+ if (colonIdx > 0) return {
659
+ title: trimmed.slice(0, colonIdx).trim(),
660
+ time: trimmed.slice(colonIdx + 1).trim()
661
+ };
662
+ return { title: trimmed };
663
+ });
664
+ const card = createAgendaCard({
665
+ title: title || "Agenda",
666
+ events
667
+ });
668
+ lineData.flexMessage = {
669
+ altText: `📋 ${title} (${events.length} events)`,
670
+ contents: card
671
+ };
672
+ }
673
+ text = text.replace(agendaMatch[0], "").trim();
674
+ }
675
+ const deviceMatch = text.match(/\[\[device:\s*([^\]]+)\]\]/i);
676
+ if (deviceMatch && !lineData.flexMessage) {
677
+ const parts = deviceMatch[1].split("|").map((s) => s.trim());
678
+ if (parts.length >= 1) {
679
+ const [deviceName, deviceType, status, controlsStr] = parts;
680
+ const deviceKey = toSlug(deviceName || "device");
681
+ const controls = controlsStr ? controlsStr.split(",").map((ctrlStr) => {
682
+ const [label, data] = ctrlStr.split(":").map((s) => s.trim());
683
+ return {
684
+ label,
685
+ data: lineActionData(data || normalizeLowercaseStringOrEmpty(label).replace(/\s+/g, "_"), { "line.device": deviceKey })
686
+ };
687
+ }) : [];
688
+ const card = createDeviceControlCard({
689
+ deviceName: deviceName || "Device",
690
+ deviceType: deviceType || void 0,
691
+ status: status || void 0,
692
+ controls
693
+ });
694
+ lineData.flexMessage = {
695
+ altText: `📱 ${deviceName}${status ? `: ${status}` : ""}`,
696
+ contents: card
697
+ };
698
+ }
699
+ text = text.replace(deviceMatch[0], "").trim();
700
+ }
701
+ text = text.replace(/\n{3,}/g, "\n\n").trim();
702
+ result.text = text || void 0;
703
+ if (Object.keys(lineData).length > 0) result.channelData = {
704
+ ...result.channelData,
705
+ line: lineData
706
+ };
707
+ return result;
708
+ }
709
+ function hasLineDirectives(text) {
710
+ return /\[\[(quick_replies|location|confirm|buttons|media_player|event|agenda|device|appletv_remote):/i.test(text);
711
+ }
712
+ //#endregion
713
+ export { createMediaPlayerCard as a, createDeviceControlCard as i, parseLineDirectives as n, LineChannelConfigSchema as o, createAppleTvRemoteCard as r, LineConfigSchema as s, hasLineDirectives as t };