@openclaw/line 2026.9.1-beta.1 → 2026.9.1
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/account-helpers-BSUyZwLm.js +12 -0
- package/dist/{accounts-BqEVsDoB.js → accounts-BBEFMYbY.js} +2 -1
- package/dist/api.js +2 -2
- package/dist/card-command-pwvE48Wb.js +252 -0
- package/dist/{channel-CsQLcrsA.js → channel-CF2AeULJ.js} +111 -380
- package/dist/channel-plugin-api.js +1 -1
- package/dist/channel.runtime-BF4Wk2dM.js +4 -0
- package/dist/contract-api.js +1 -1
- package/dist/doctor-contract-api.js +141 -0
- package/dist/index.js +5 -17
- package/dist/{markdown-to-line-BmZ4hhfC.js → markdown-to-line-B-sj-7II.js} +22 -23
- package/dist/{monitor-Bd9XDV1i.js → monitor-D0fHFXCi.js} +323 -234
- package/dist/monitor.runtime-Mgecav_D.js +2 -0
- package/dist/outbound.runtime-CGqyJ-Ug.js +3 -0
- package/dist/probe.runtime-CbtMgU4s.js +2 -0
- package/dist/rich-messages-DY1AGKAJ.js +742 -0
- package/dist/runtime-api.js +14 -15
- package/dist/{send-Dce0m539.js → send-CQQxpxPp.js} +142 -112
- package/dist/{send-receipt-CCPTalf9.js → send-retry-ChpfJ-LM.js} +326 -86
- package/dist/setup-api.js +1 -1
- package/dist/{setup-surface-W0Dbgu58.js → setup-surface-CDlgkTKi.js} +47 -39
- package/dist/webhook-spool-contract-1rY8OJw8.js +59 -0
- package/node_modules/typebox/build/format/email.mjs +1 -1
- package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
- package/node_modules/typebox/package.json +1 -1
- package/openclaw.plugin.json +16 -2
- package/package.json +5 -5
- package/dist/card-command-CmU47gD9.js +0 -230
- package/dist/channel.runtime-B83x7k_i.js +0 -4
- package/dist/media-control-cards-CksNB8Wk.js +0 -474
- package/dist/monitor.runtime-BCCa6qTX.js +0 -2
- package/dist/outbound.runtime-D4w0-Gkc.js +0 -3
- package/dist/probe-CM03wo6r.js +0 -28
- package/dist/probe.runtime-D13ZtXsd.js +0 -2
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { isRecord, normalizeNullableString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
2
|
+
//#region extensions/line/src/webhook-spool-contract.ts
|
|
3
|
+
/** Message the canonical decoder attaches when it rejects a spool payload. The
|
|
4
|
+
* upgrade migration matches this signature to recover rows the pre-fix decoder
|
|
5
|
+
* dead-lettered; the identity fence writes a different message on purpose. */
|
|
6
|
+
const LINE_WEBHOOK_SPOOL_INVALID_PAYLOAD_MESSAGE = "LINE webhook spool payload is invalid.";
|
|
7
|
+
/** Dead-letter reason for undecodable events; shared so the migration's
|
|
8
|
+
* recovery signature can never drift from what the spool writes. */
|
|
9
|
+
const LINE_WEBHOOK_SPOOL_INVALID_EVENT_REASON = "invalid-event";
|
|
10
|
+
/** Defined locally (not via createChannelIngressError) because this module sits
|
|
11
|
+
* in the doctor contract closure, which must stay off the channel-outbound
|
|
12
|
+
* runtime barrel; the spool's permanent-failure classifier matches by class. */
|
|
13
|
+
var LineWebhookPayloadError = class extends Error {
|
|
14
|
+
constructor(message, options) {
|
|
15
|
+
super(message, options);
|
|
16
|
+
this.name = "LineWebhookPayloadError";
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/** Message ids preserve the shipped replay-guard keyspace; other events use LINE's delivery id. */
|
|
20
|
+
function eventIdFor(event) {
|
|
21
|
+
if (!isRecord(event)) throw new LineWebhookPayloadError("LINE webhook event must be an object.");
|
|
22
|
+
if (event.type === "message") {
|
|
23
|
+
const message = isRecord(event.message) ? event.message : void 0;
|
|
24
|
+
const messageId = normalizeNullableString(message?.id);
|
|
25
|
+
if (messageId) return `message:${messageId}`;
|
|
26
|
+
}
|
|
27
|
+
const webhookEventId = normalizeNullableString(event.webhookEventId);
|
|
28
|
+
if (webhookEventId) return `event:${webhookEventId}`;
|
|
29
|
+
throw new LineWebhookPayloadError("LINE webhook event is missing a stable delivery id.");
|
|
30
|
+
}
|
|
31
|
+
/** Pre-drain (#109655) rows were keyed by the raw webhookEventId, before the
|
|
32
|
+
* message:/event: keyspace. The upgrade migration uses this prior derivation as its
|
|
33
|
+
* identity fence so a genuinely changed event still dead-letters. */
|
|
34
|
+
function legacyEventIdFor(event) {
|
|
35
|
+
if (!isRecord(event)) return null;
|
|
36
|
+
return normalizeNullableString(event.webhookEventId);
|
|
37
|
+
}
|
|
38
|
+
function laneKeyFor(event, eventId) {
|
|
39
|
+
if (!isRecord(event)) return eventId;
|
|
40
|
+
const source = isRecord(event.source) ? event.source : void 0;
|
|
41
|
+
if (source?.type === "group") {
|
|
42
|
+
const groupId = normalizeNullableString(source.groupId);
|
|
43
|
+
if (groupId) return `group:${groupId}`;
|
|
44
|
+
}
|
|
45
|
+
if (source?.type === "room") {
|
|
46
|
+
const roomId = normalizeNullableString(source.roomId);
|
|
47
|
+
if (roomId) return `room:${roomId}`;
|
|
48
|
+
}
|
|
49
|
+
if (source?.type === "user") {
|
|
50
|
+
const userId = normalizeNullableString(source.userId);
|
|
51
|
+
if (userId) return `user:${userId}`;
|
|
52
|
+
}
|
|
53
|
+
return eventId;
|
|
54
|
+
}
|
|
55
|
+
function errorText(error) {
|
|
56
|
+
return error instanceof Error ? error.message : String(error);
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { eventIdFor as a, errorText as i, LINE_WEBHOOK_SPOOL_INVALID_PAYLOAD_MESSAGE as n, laneKeyFor as o, LineWebhookPayloadError as r, legacyEventIdFor as s, LINE_WEBHOOK_SPOOL_INVALID_EVENT_REASON as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const Email = /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[^"\\]
|
|
1
|
+
const Email = /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[^"\\]|\\[\x20-\x7e])*")@(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*|\[(?:IPv6:[a-f0-9:]+|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})\])$/i;
|
|
2
2
|
/**
|
|
3
3
|
* Returns true if the value is an Email
|
|
4
4
|
* @specification https://datatracker.ietf.org/doc/html/rfc5322
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import * as FormatBidi from './format/bidi.mjs';
|
|
2
2
|
import * as LabelUnicode from './label/unicode.mjs';
|
|
3
3
|
import * as LabelPuny from './label/puny.mjs';
|
|
4
|
+
// ------------------------------------------------------------------
|
|
5
|
+
// IsLabel
|
|
6
|
+
// ------------------------------------------------------------------
|
|
4
7
|
function IsValidLabelLength(value) {
|
|
5
8
|
return value.length > 0 && value.length <= 63;
|
|
6
9
|
}
|
|
@@ -8,9 +11,24 @@ function IsLabel(value) {
|
|
|
8
11
|
return IsValidLabelLength(value) && (LabelPuny.IsPunyLabel(value) ||
|
|
9
12
|
LabelUnicode.IsUnicodeLabel(value));
|
|
10
13
|
}
|
|
14
|
+
// ------------------------------------------------------------------
|
|
15
|
+
// NormalizeHostname
|
|
16
|
+
//
|
|
17
|
+
// Normalizes a hostname for label validation. Normalizes for NFC
|
|
18
|
+
// such that equivalent codepoint sequences can be uniformly
|
|
19
|
+
// compared. It also removes codepoints that IDNA mapping ignores,
|
|
20
|
+
// and converts full stop variants to ASCII '.' so the hostname can
|
|
21
|
+
// be split into labels.
|
|
22
|
+
// ------------------------------------------------------------------
|
|
11
23
|
function NormalizeHostname(value) {
|
|
12
|
-
return value
|
|
24
|
+
return value
|
|
25
|
+
.normalize('NFC')
|
|
26
|
+
.replace(/[\u00ad\u034f\u180b-\u180d\u200b\ufe00-\ufe0f\u{e0100}-\u{e01ef}]/gu, '') // RE_IGNORED_MAPPING
|
|
27
|
+
.replace(/[\u002E\u3002\uFF0E\uFF61]/g, '.'); // RE_FULL_STOP_MAPPING
|
|
13
28
|
}
|
|
29
|
+
// ------------------------------------------------------------------
|
|
30
|
+
// IsIdnHostname
|
|
31
|
+
// ------------------------------------------------------------------
|
|
14
32
|
export function IsIdnHostname(value) {
|
|
15
33
|
if (value.length === 0 || value.includes(' '))
|
|
16
34
|
return false;
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "line",
|
|
3
|
-
"doctorContract": {
|
|
3
|
+
"doctorContract": {
|
|
4
|
+
"stateMigrations": true
|
|
5
|
+
},
|
|
4
6
|
"name": "LINE",
|
|
5
7
|
"description": "OpenClaw LINE channel plugin for LINE Bot API chats.",
|
|
6
8
|
"icon": "https://cdn.simpleicons.org/line",
|
|
@@ -27,6 +29,9 @@
|
|
|
27
29
|
"configWrites": {
|
|
28
30
|
"type": "boolean"
|
|
29
31
|
},
|
|
32
|
+
"joinIntro": {
|
|
33
|
+
"type": "boolean"
|
|
34
|
+
},
|
|
30
35
|
"historyLimit": {
|
|
31
36
|
"type": "integer",
|
|
32
37
|
"minimum": 0,
|
|
@@ -180,6 +185,9 @@
|
|
|
180
185
|
"configWrites": {
|
|
181
186
|
"type": "boolean"
|
|
182
187
|
},
|
|
188
|
+
"joinIntro": {
|
|
189
|
+
"type": "boolean"
|
|
190
|
+
},
|
|
183
191
|
"historyLimit": {
|
|
184
192
|
"type": "integer",
|
|
185
193
|
"minimum": 0,
|
|
@@ -338,7 +346,13 @@
|
|
|
338
346
|
"additionalProperties": false
|
|
339
347
|
},
|
|
340
348
|
"label": "LINE",
|
|
341
|
-
"description": "LINE Messaging API webhook bot."
|
|
349
|
+
"description": "LINE Messaging API webhook bot.",
|
|
350
|
+
"uiHints": {
|
|
351
|
+
"joinIntro": {
|
|
352
|
+
"label": "LINE Group Join Introduction",
|
|
353
|
+
"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."
|
|
354
|
+
}
|
|
355
|
+
}
|
|
342
356
|
}
|
|
343
357
|
}
|
|
344
358
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/line",
|
|
3
|
-
"version": "2026.9.1
|
|
3
|
+
"version": "2026.9.1",
|
|
4
4
|
"description": "OpenClaw LINE channel plugin for LINE Bot API chats.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"type": "module",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@line/bot-sdk": "11.2.0",
|
|
12
|
-
"typebox": "1.3.
|
|
12
|
+
"typebox": "1.3.17",
|
|
13
13
|
"zod": "4.4.3"
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
|
-
"openclaw": ">=2026.9.1
|
|
16
|
+
"openclaw": ">=2026.9.1"
|
|
17
17
|
},
|
|
18
18
|
"peerDependenciesMeta": {
|
|
19
19
|
"openclaw": {
|
|
@@ -112,10 +112,10 @@
|
|
|
112
112
|
"minHostVersion": ">=2026.4.10"
|
|
113
113
|
},
|
|
114
114
|
"compat": {
|
|
115
|
-
"pluginApi": ">=2026.9.1
|
|
115
|
+
"pluginApi": ">=2026.9.1"
|
|
116
116
|
},
|
|
117
117
|
"build": {
|
|
118
|
-
"openclawVersion": "2026.9.1
|
|
118
|
+
"openclawVersion": "2026.9.1"
|
|
119
119
|
},
|
|
120
120
|
"release": {
|
|
121
121
|
"publishToClawHub": true,
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import { a as createActionCard, c as createListCard, i as createReceiptCard, o as createImageCard, s as createInfoCard } from "./send-receipt-CCPTalf9.js";
|
|
2
|
-
import { n as createFlexMessage } from "./send-Dce0m539.js";
|
|
3
|
-
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
4
|
-
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
5
|
-
//#region extensions/line/src/card-command.ts
|
|
6
|
-
const CARD_USAGE = `Usage: /card <type> "title" "body" [options]
|
|
7
|
-
|
|
8
|
-
Types:
|
|
9
|
-
info "Title" "Body" ["Footer"]
|
|
10
|
-
image "Title" "Caption" --url <image-url>
|
|
11
|
-
action "Title" "Body" --actions "Btn1|url1,Btn2|text2"
|
|
12
|
-
list "Title" "Item1|Desc1,Item2|Desc2"
|
|
13
|
-
receipt "Title" "Item1:$10,Item2:$20" --total "$30"
|
|
14
|
-
confirm "Question?" --yes "Yes|data" --no "No|data"
|
|
15
|
-
buttons "Title" "Text" --actions "Btn1|url1,Btn2|data2"
|
|
16
|
-
|
|
17
|
-
Examples:
|
|
18
|
-
/card info "Welcome" "Thanks for joining!"
|
|
19
|
-
/card image "Product" "Check it out" --url https://example.com/img.jpg
|
|
20
|
-
/card action "Menu" "Choose an option" --actions "Order|/order,Help|/help"`;
|
|
21
|
-
function buildLineReply(lineData) {
|
|
22
|
-
return { channelData: { line: lineData } };
|
|
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
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Parse action string format: "Label|data,Label2|data2"
|
|
33
|
-
* Data can be a URL (uri action) or plain text (message action) or key=value (postback)
|
|
34
|
-
*/
|
|
35
|
-
function parseActions(actionsStr) {
|
|
36
|
-
if (!actionsStr) return [];
|
|
37
|
-
const results = [];
|
|
38
|
-
for (const part of actionsStr.split(",")) {
|
|
39
|
-
const [label, data] = part.trim().split("|").map((s) => s.trim());
|
|
40
|
-
if (!label) continue;
|
|
41
|
-
const actionData = data || label;
|
|
42
|
-
const action = actionData.startsWith("http://") || actionData.startsWith("https://") ? {
|
|
43
|
-
type: "uri",
|
|
44
|
-
label,
|
|
45
|
-
uri: actionData
|
|
46
|
-
} : actionData.includes("=") ? {
|
|
47
|
-
type: "postback",
|
|
48
|
-
label,
|
|
49
|
-
data: actionData,
|
|
50
|
-
displayText: label
|
|
51
|
-
} : {
|
|
52
|
-
type: "message",
|
|
53
|
-
label,
|
|
54
|
-
text: actionData
|
|
55
|
-
};
|
|
56
|
-
results.push({
|
|
57
|
-
label,
|
|
58
|
-
action
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
return results;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Parse list items format: "Item1|Subtitle1,Item2|Subtitle2"
|
|
65
|
-
*/
|
|
66
|
-
function parseListItems(itemsStr) {
|
|
67
|
-
return itemsStr.split(",").map((part) => {
|
|
68
|
-
const [title, subtitle] = part.trim().split("|").map((s) => s.trim());
|
|
69
|
-
return {
|
|
70
|
-
title: title || "",
|
|
71
|
-
subtitle
|
|
72
|
-
};
|
|
73
|
-
}).filter((item) => item.title);
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Parse receipt items format: "Item1:$10,Item2:$20"
|
|
77
|
-
*/
|
|
78
|
-
function parseReceiptItems(itemsStr) {
|
|
79
|
-
return itemsStr.split(",").map((part) => {
|
|
80
|
-
const colonIndex = part.lastIndexOf(":");
|
|
81
|
-
if (colonIndex === -1) return {
|
|
82
|
-
name: part.trim(),
|
|
83
|
-
value: ""
|
|
84
|
-
};
|
|
85
|
-
return {
|
|
86
|
-
name: part.slice(0, colonIndex).trim(),
|
|
87
|
-
value: part.slice(colonIndex + 1).trim()
|
|
88
|
-
};
|
|
89
|
-
}).filter((item) => item.name);
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Parse quoted arguments from command string
|
|
93
|
-
* Supports: /card type "arg1" "arg2" "arg3" --flag value
|
|
94
|
-
*/
|
|
95
|
-
function parseCardArgs(argsStrInput) {
|
|
96
|
-
let argsStr = argsStrInput;
|
|
97
|
-
const result = {
|
|
98
|
-
type: "",
|
|
99
|
-
args: [],
|
|
100
|
-
flags: {}
|
|
101
|
-
};
|
|
102
|
-
const typeMatch = argsStr.match(/^(\w+)/);
|
|
103
|
-
if (typeMatch) {
|
|
104
|
-
result.type = normalizeLowercaseStringOrEmpty(typeMatch[1]);
|
|
105
|
-
argsStr = argsStr.slice(typeMatch[0].length).trim();
|
|
106
|
-
}
|
|
107
|
-
const quotedRegex = /"([^"]*?)"/g;
|
|
108
|
-
let match;
|
|
109
|
-
while ((match = quotedRegex.exec(argsStr)) !== null) result.args.push(expectDefined(match[1], "quoted card argument capture"));
|
|
110
|
-
const flagRegex = /--(\w+)\s+(?:"([^"]*?)"|(\S+))/g;
|
|
111
|
-
while ((match = flagRegex.exec(argsStr)) !== null) {
|
|
112
|
-
const key = expectDefined(match[1], "card flag name capture");
|
|
113
|
-
result.flags[key] = expectDefined(match[2] ?? match[3], "card flag value capture");
|
|
114
|
-
}
|
|
115
|
-
return result;
|
|
116
|
-
}
|
|
117
|
-
function registerLineCardCommand(api) {
|
|
118
|
-
api.registerCommand({
|
|
119
|
-
name: "card",
|
|
120
|
-
description: "Send a rich card message (LINE).",
|
|
121
|
-
acceptsArgs: true,
|
|
122
|
-
requireAuth: false,
|
|
123
|
-
handler: async (ctx) => {
|
|
124
|
-
const argsStr = ctx.args?.trim() ?? "";
|
|
125
|
-
if (!argsStr) return { text: CARD_USAGE };
|
|
126
|
-
const { type, args, flags } = parseCardArgs(argsStr);
|
|
127
|
-
if (!type) return { text: CARD_USAGE };
|
|
128
|
-
if (ctx.channel !== "line") return { text: `[${type} card] ${args.join(" - ")}`.trim() };
|
|
129
|
-
try {
|
|
130
|
-
switch (type) {
|
|
131
|
-
case "info": {
|
|
132
|
-
const [title = "Info", body = "", footer] = args;
|
|
133
|
-
const bubble = createInfoCard(title, body, footer);
|
|
134
|
-
return buildLineFlexReply(`${title}: ${body}`, bubble);
|
|
135
|
-
}
|
|
136
|
-
case "image": {
|
|
137
|
-
const [title = "Image", caption = ""] = args;
|
|
138
|
-
const imageUrl = flags.url || flags.image;
|
|
139
|
-
if (!imageUrl) return { text: "Error: Image card requires --url <image-url>" };
|
|
140
|
-
const bubble = createImageCard(imageUrl, title, caption);
|
|
141
|
-
return buildLineFlexReply(`${title}: ${caption}`, bubble);
|
|
142
|
-
}
|
|
143
|
-
case "action": {
|
|
144
|
-
const [title = "Actions", body = ""] = args;
|
|
145
|
-
const actions = parseActions(flags.actions);
|
|
146
|
-
if (actions.length === 0) return { text: "Error: Action card requires --actions \"Label1|data1,Label2|data2\"" };
|
|
147
|
-
const bubble = createActionCard(title, body, actions, { imageUrl: flags.url || flags.image });
|
|
148
|
-
return buildLineFlexReply(`${title}: ${body}`, bubble);
|
|
149
|
-
}
|
|
150
|
-
case "list": {
|
|
151
|
-
const [title = "List", itemsStr = ""] = args;
|
|
152
|
-
const items = parseListItems(itemsStr || flags.items || "");
|
|
153
|
-
if (items.length === 0) return { text: "Error: List card requires items. Usage: /card list \"Title\" \"Item1|Desc1,Item2|Desc2\"" };
|
|
154
|
-
const bubble = createListCard(title, items);
|
|
155
|
-
return buildLineFlexReply(`${title}: ${items.map((item) => item.title).join(", ")}`, bubble);
|
|
156
|
-
}
|
|
157
|
-
case "receipt": {
|
|
158
|
-
const [title = "Receipt", itemsStr = ""] = args;
|
|
159
|
-
const items = parseReceiptItems(itemsStr || flags.items || "");
|
|
160
|
-
const total = flags.total ? {
|
|
161
|
-
label: "Total",
|
|
162
|
-
value: flags.total
|
|
163
|
-
} : void 0;
|
|
164
|
-
const footer = flags.footer;
|
|
165
|
-
if (items.length === 0) return { text: "Error: Receipt card requires items. Usage: /card receipt \"Title\" \"Item1:$10,Item2:$20\" --total \"$30\"" };
|
|
166
|
-
const bubble = createReceiptCard({
|
|
167
|
-
title,
|
|
168
|
-
items,
|
|
169
|
-
total,
|
|
170
|
-
footer
|
|
171
|
-
});
|
|
172
|
-
return buildLineFlexReply(`${title}: ${items.map((item) => `${item.name} ${item.value}`).join(", ")}`, bubble);
|
|
173
|
-
}
|
|
174
|
-
case "confirm": {
|
|
175
|
-
const [question = "Confirm?"] = args;
|
|
176
|
-
const yesStr = flags.yes || "Yes|yes";
|
|
177
|
-
const noStr = flags.no || "No|no";
|
|
178
|
-
const [yesLabel, yesData] = yesStr.split("|").map((s) => s.trim());
|
|
179
|
-
const [noLabel, noData] = noStr.split("|").map((s) => s.trim());
|
|
180
|
-
return buildLineReply({ templateMessage: {
|
|
181
|
-
type: "confirm",
|
|
182
|
-
text: question,
|
|
183
|
-
confirmLabel: yesLabel || "Yes",
|
|
184
|
-
confirmData: yesData || "yes",
|
|
185
|
-
cancelLabel: noLabel || "No",
|
|
186
|
-
cancelData: noData || "no",
|
|
187
|
-
altText: question
|
|
188
|
-
} });
|
|
189
|
-
}
|
|
190
|
-
case "buttons": {
|
|
191
|
-
const [title = "Menu", text = "Choose an option"] = args;
|
|
192
|
-
const actionParts = parseActions(flags.actions || "");
|
|
193
|
-
if (actionParts.length === 0) return { text: "Error: Buttons card requires --actions \"Label1|data1,Label2|data2\"" };
|
|
194
|
-
const templateActions = actionParts.map((a) => {
|
|
195
|
-
const action = a.action;
|
|
196
|
-
const label = action.label ?? a.label;
|
|
197
|
-
if (action.type === "uri") return {
|
|
198
|
-
type: "uri",
|
|
199
|
-
label,
|
|
200
|
-
uri: action.uri
|
|
201
|
-
};
|
|
202
|
-
if (action.type === "postback") return {
|
|
203
|
-
type: "postback",
|
|
204
|
-
label,
|
|
205
|
-
data: action.data
|
|
206
|
-
};
|
|
207
|
-
return {
|
|
208
|
-
type: "message",
|
|
209
|
-
label,
|
|
210
|
-
data: action.text
|
|
211
|
-
};
|
|
212
|
-
});
|
|
213
|
-
return buildLineReply({ templateMessage: {
|
|
214
|
-
type: "buttons",
|
|
215
|
-
title,
|
|
216
|
-
text,
|
|
217
|
-
thumbnailImageUrl: flags.url || flags.image,
|
|
218
|
-
actions: templateActions
|
|
219
|
-
} });
|
|
220
|
-
}
|
|
221
|
-
default: return { text: `Unknown card type: "${type}". Available types: info, image, action, list, receipt, confirm, buttons` };
|
|
222
|
-
}
|
|
223
|
-
} catch (err) {
|
|
224
|
-
return { text: `Error creating card: ${String(err)}` };
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
//#endregion
|
|
230
|
-
export { registerLineCardCommand };
|