@openclaw/line 2026.9.1-beta.1 → 2026.9.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 (46) hide show
  1. package/dist/account-helpers-BSUyZwLm.js +12 -0
  2. package/dist/{accounts-BqEVsDoB.js → accounts-BBEFMYbY.js} +2 -1
  3. package/dist/api.js +2 -2
  4. package/dist/card-command-pwvE48Wb.js +252 -0
  5. package/dist/{channel-CsQLcrsA.js → channel-BJvELOIg.js} +111 -380
  6. package/dist/channel-plugin-api.js +1 -1
  7. package/dist/channel.runtime-CZOszXXj.js +4 -0
  8. package/dist/contract-api.js +1 -1
  9. package/dist/doctor-contract-api.js +141 -0
  10. package/dist/index.js +5 -17
  11. package/dist/{markdown-to-line-BmZ4hhfC.js → markdown-to-line-B-sj-7II.js} +22 -23
  12. package/dist/{monitor-Bd9XDV1i.js → monitor-m9h5osFz.js} +339 -237
  13. package/dist/monitor.runtime-C90LL-P3.js +2 -0
  14. package/dist/outbound.runtime-CGqyJ-Ug.js +3 -0
  15. package/dist/probe.runtime-CbtMgU4s.js +2 -0
  16. package/dist/rich-messages-DY1AGKAJ.js +742 -0
  17. package/dist/runtime-api.js +14 -15
  18. package/dist/{send-Dce0m539.js → send-CQQxpxPp.js} +142 -112
  19. package/dist/{send-receipt-CCPTalf9.js → send-retry-ChpfJ-LM.js} +326 -86
  20. package/dist/setup-api.js +1 -1
  21. package/dist/{setup-surface-W0Dbgu58.js → setup-surface-CDlgkTKi.js} +47 -39
  22. package/dist/webhook-spool-contract-1rY8OJw8.js +59 -0
  23. package/node_modules/typebox/build/format/email.mjs +1 -1
  24. package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
  25. package/node_modules/typebox/build/format/json_pointer.d.mts +2 -2
  26. package/node_modules/typebox/build/format/json_pointer.mjs +2 -2
  27. package/node_modules/typebox/build/format/json_pointer_uri_fragment.d.mts +2 -2
  28. package/node_modules/typebox/build/format/json_pointer_uri_fragment.mjs +2 -2
  29. package/node_modules/typebox/build/format/relative_json_pointer.d.mts +2 -2
  30. package/node_modules/typebox/build/format/relative_json_pointer.mjs +2 -2
  31. package/node_modules/typebox/build/system/memory/assign.mjs +2 -1
  32. package/node_modules/typebox/build/system/memory/create.mjs +3 -4
  33. package/node_modules/typebox/build/system/memory/discard.mjs +2 -2
  34. package/node_modules/typebox/build/system/memory/freeze.d.mts +4 -0
  35. package/node_modules/typebox/build/system/memory/freeze.mjs +6 -0
  36. package/node_modules/typebox/build/system/memory/update.mjs +3 -2
  37. package/node_modules/typebox/package.json +1 -1
  38. package/openclaw.plugin.json +16 -3
  39. package/package.json +5 -5
  40. package/dist/card-command-CmU47gD9.js +0 -230
  41. package/dist/channel.runtime-B83x7k_i.js +0 -4
  42. package/dist/media-control-cards-CksNB8Wk.js +0 -474
  43. package/dist/monitor.runtime-BCCa6qTX.js +0 -2
  44. package/dist/outbound.runtime-D4w0-Gkc.js +0 -3
  45. package/dist/probe-CM03wo6r.js +0 -28
  46. 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!#$%&'*+/=?^_`{|}~-]+)*|"(?:[^"\\]|\\.)*")@(?:[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;
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.normalize('NFC').replace(/[\u002E\u3002\uFF0E\uFF61]/g, '.');
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;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Returns true if the value is a json pointer
3
- * @specification
4
- * @source ajv-formats
3
+ * @specification https://datatracker.ietf.org/doc/html/rfc6901
4
+ * @source https://github.com/ajv-validator/ajv-formats
5
5
  */
6
6
  export declare function IsJsonPointer(value: string): boolean;
@@ -1,8 +1,8 @@
1
1
  const JsonPointer = /^(?:\/(?:[^~/]|~0|~1)*)*$/;
2
2
  /**
3
3
  * Returns true if the value is a json pointer
4
- * @specification
5
- * @source ajv-formats
4
+ * @specification https://datatracker.ietf.org/doc/html/rfc6901
5
+ * @source https://github.com/ajv-validator/ajv-formats
6
6
  */
7
7
  export function IsJsonPointer(value) {
8
8
  return JsonPointer.test(value);
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Returns true if the value is a json pointer uri fragment
3
- * @specification
4
- * @source ajv-formats
3
+ * @specification https://datatracker.ietf.org/doc/html/rfc6901
4
+ * @source https://github.com/ajv-validator/ajv-formats
5
5
  */
6
6
  export declare function IsJsonPointerUriFragment(value: string): boolean;
@@ -1,8 +1,8 @@
1
1
  const JsonPointerUriFragment = /^#(?:\/(?:[a-z0-9_\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i;
2
2
  /**
3
3
  * Returns true if the value is a json pointer uri fragment
4
- * @specification
5
- * @source ajv-formats
4
+ * @specification https://datatracker.ietf.org/doc/html/rfc6901
5
+ * @source https://github.com/ajv-validator/ajv-formats
6
6
  */
7
7
  export function IsJsonPointerUriFragment(value) {
8
8
  return JsonPointerUriFragment.test(value);
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Returns true if the value is a relative json pointer
3
- * @specification
4
- * @source ajv-formats
3
+ * @specification https://datatracker.ietf.org/doc/html/rfc6901
4
+ * @source https://github.com/ajv-validator/ajv-formats
5
5
  */
6
6
  export declare function IsRelativeJsonPointer(value: string): boolean;
@@ -1,8 +1,8 @@
1
1
  const RelativeJsonPointer = /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/;
2
2
  /**
3
3
  * Returns true if the value is a relative json pointer
4
- * @specification
5
- * @source ajv-formats
4
+ * @specification https://datatracker.ietf.org/doc/html/rfc6901
5
+ * @source https://github.com/ajv-validator/ajv-formats
6
6
  */
7
7
  export function IsRelativeJsonPointer(value) {
8
8
  return RelativeJsonPointer.test(value);
@@ -1,11 +1,12 @@
1
1
  // deno-lint-ignore-file ban-types no-explicit-any
2
2
  // deno-fmt-ignore-file
3
3
  import { Metrics } from './metrics.mjs';
4
+ import { Freeze } from './freeze.mjs';
4
5
  /**
5
6
  * Performs an Object assign using the Left and Right object types. We track this operation as it
6
7
  * creates a new GC handle per assignment.
7
8
  */
8
9
  export function Assign(left, right) {
9
10
  Metrics.assign += 1;
10
- return { ...left, ...right };
11
+ return Freeze({ ...left, ...right });
11
12
  }
@@ -1,7 +1,7 @@
1
1
  // deno-lint-ignore-file no-explicit-any
2
- // deno-fmt-ignore-file
3
2
  import { Settings } from '../settings/index.mjs';
4
3
  import { Metrics } from './metrics.mjs';
4
+ import { Freeze } from './freeze.mjs';
5
5
  function MergeHidden(left, right) {
6
6
  for (const key of Object.keys(right)) {
7
7
  Object.defineProperty(left, key, {
@@ -23,8 +23,7 @@ function Merge(left, right) {
23
23
  */
24
24
  export function Create(hidden, enumerable, options = {}) {
25
25
  Metrics.create += 1;
26
- const settings = Settings.Get();
27
26
  const withOptions = Merge(enumerable, options);
28
- const withHidden = settings.enumerableKind ? Merge(withOptions, hidden) : MergeHidden(withOptions, hidden);
29
- return settings.immutableTypes ? Object.freeze(withHidden) : withHidden;
27
+ const withHidden = Settings.Get().enumerableKind ? Merge(withOptions, hidden) : MergeHidden(withOptions, hidden);
28
+ return Freeze(withHidden);
30
29
  }
@@ -1,7 +1,7 @@
1
1
  // deno-lint-ignore-file no-explicit-any
2
- // deno-fmt-ignore-file
3
2
  import { Guard } from '../../guard/index.mjs';
4
3
  import { Metrics } from './metrics.mjs';
4
+ import { Freeze } from './freeze.mjs';
5
5
  import { Clone } from './clone.mjs';
6
6
  /** Discards multiple property keys from the given object value */
7
7
  export function Discard(value, propertyKeys) {
@@ -14,5 +14,5 @@ export function Discard(value, propertyKeys) {
14
14
  descriptor.value = Clone(descriptor.value);
15
15
  Object.defineProperty(result, key, descriptor);
16
16
  }
17
- return result;
17
+ return Freeze(result);
18
18
  }
@@ -0,0 +1,4 @@
1
+ type ObjectLike = Record<PropertyKey, any>;
2
+ /** Conditionally freezes the value if `immutableTypes` is true, otherwise no action. */
3
+ export declare function Freeze(value: ObjectLike): ObjectLike;
4
+ export {};
@@ -0,0 +1,6 @@
1
+ // deno-lint-ignore-file no-explicit-any
2
+ import { Settings } from '../settings/index.mjs';
3
+ /** Conditionally freezes the value if `immutableTypes` is true, otherwise no action. */
4
+ export function Freeze(value) {
5
+ return Settings.Get().immutableTypes ? Object.freeze(value) : value;
6
+ }
@@ -1,6 +1,7 @@
1
- // deno-fmt-ignore-file
1
+ // deno-lint-ignore-file no-explicit-any
2
2
  import { Settings } from '../settings/index.mjs';
3
3
  import { Metrics } from './metrics.mjs';
4
+ import { Freeze } from './freeze.mjs';
4
5
  import { Clone } from './clone.mjs';
5
6
  /**
6
7
  * Updates a value with new properties while preserving property enumerability. Use this function to modify
@@ -28,5 +29,5 @@ export function Update(current, hidden, enumerable) {
28
29
  value: enumerable[key]
29
30
  });
30
31
  }
31
- return result;
32
+ return Freeze(result);
32
33
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "typebox",
3
3
  "description": "Json Schema Type Builder with Static Type Resolution for TypeScript",
4
- "version": "1.3.16",
4
+ "version": "1.3.18",
5
5
  "keywords": [
6
6
  "typescript",
7
7
  "jsonschema"
@@ -1,9 +1,10 @@
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
- "icon": "https://cdn.simpleicons.org/line",
7
8
  "activation": {
8
9
  "onStartup": false
9
10
  },
@@ -27,6 +28,9 @@
27
28
  "configWrites": {
28
29
  "type": "boolean"
29
30
  },
31
+ "joinIntro": {
32
+ "type": "boolean"
33
+ },
30
34
  "historyLimit": {
31
35
  "type": "integer",
32
36
  "minimum": 0,
@@ -180,6 +184,9 @@
180
184
  "configWrites": {
181
185
  "type": "boolean"
182
186
  },
187
+ "joinIntro": {
188
+ "type": "boolean"
189
+ },
183
190
  "historyLimit": {
184
191
  "type": "integer",
185
192
  "minimum": 0,
@@ -338,7 +345,13 @@
338
345
  "additionalProperties": false
339
346
  },
340
347
  "label": "LINE",
341
- "description": "LINE Messaging API webhook bot."
348
+ "description": "LINE Messaging API webhook bot.",
349
+ "uiHints": {
350
+ "joinIntro": {
351
+ "label": "LINE Group Join Introduction",
352
+ "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."
353
+ }
354
+ }
342
355
  }
343
356
  }
344
357
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/line",
3
- "version": "2026.9.1-beta.1",
3
+ "version": "2026.9.2",
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.16",
12
+ "typebox": "1.3.18",
13
13
  "zod": "4.4.3"
14
14
  },
15
15
  "peerDependencies": {
16
- "openclaw": ">=2026.9.1-beta.1"
16
+ "openclaw": ">=2026.9.2"
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-beta.1"
115
+ "pluginApi": ">=2026.9.2"
116
116
  },
117
117
  "build": {
118
- "openclawVersion": "2026.9.1-beta.1"
118
+ "openclawVersion": "2026.9.2"
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 };
@@ -1,4 +0,0 @@
1
- import { t as monitorLineProvider } from "./monitor-Bd9XDV1i.js";
2
- import { t as probeLineBot } from "./probe-CM03wo6r.js";
3
- import { p as pushMessageLine } from "./send-Dce0m539.js";
4
- export { monitorLineProvider, probeLineBot, pushMessageLine };