@openclaw/line 2026.7.2-beta.5 → 2026.8.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{accounts-CSH5sk8U.js → accounts-BqEVsDoB.js} +24 -88
- package/dist/api.js +2 -3
- package/dist/{card-command-BEMIm8D8.js → card-command-DALdUSnc.js} +14 -22
- package/dist/{channel-CTaWrzfl.js → channel-CAdQJRw7.js} +75 -77
- package/dist/channel-plugin-api.js +1 -1
- package/dist/channel.runtime-CuKoy9xw.js +4 -0
- package/dist/contract-api.js +1 -1
- package/dist/{flex-templates-DVy_wog4.js → flex-templates-BBBlNqpV.js} +108 -83
- package/dist/index.js +1 -1
- package/dist/markdown-to-line-D4I-6lm6.js +499 -0
- package/dist/{monitor-DzFU5UPV.js → monitor-QEh_ZqyR.js} +114 -94
- package/dist/monitor.runtime-72isajCp.js +2 -0
- package/dist/outbound.runtime-CWQMIE7N.js +3 -0
- package/dist/{reply-payload-transform-BRM2fRvK.js → reply-payload-transform-BOPGHVPo.js} +4 -3
- package/dist/runtime-api.js +9 -7
- package/dist/send-CFFr0-rZ.js +366 -0
- package/dist/setup-api.js +1 -1
- package/dist/{setup-surface-BHQOSD8R.js → setup-surface-W0Dbgu58.js} +51 -68
- package/openclaw.plugin.json +7 -0
- package/package.json +9 -5
- package/dist/channel.runtime-D4Paci2y.js +0 -4
- package/dist/markdown-to-line-Dn00k1bq.js +0 -799
- package/dist/monitor.runtime-B8xjVqev.js +0 -2
- package/dist/outbound.runtime-eHl7NNHu.js +0 -2
- package/dist/send-receipt-CY2ey8XH.js +0 -107
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/LICENSE +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/README.md +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/assert/strict.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/assert.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/async_hooks.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/buffer.buffer.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/buffer.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/child_process.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/cluster.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/compatibility/iterators.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/console.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/constants.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/crypto.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/dgram.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/diagnostics_channel.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/dns/promises.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/dns.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/domain.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/events.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/fs/promises.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/fs.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/globals.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/globals.typedarray.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/http.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/http2.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/https.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/index.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/inspector.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/inspector.generated.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/module.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/net.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/os.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/package.json +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/path.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/perf_hooks.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/process.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/punycode.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/querystring.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/readline/promises.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/readline.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/repl.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/sea.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/sqlite.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/stream/consumers.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/stream/promises.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/stream/web.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/stream.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/string_decoder.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/test.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/timers/promises.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/timers.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/tls.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/trace_events.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.6/buffer.buffer.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.6/compatibility/float16array.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.6/globals.typedarray.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.6/index.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.7/compatibility/float16array.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.7/index.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/tty.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/url.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/util.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/v8.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/vm.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/wasi.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/abortcontroller.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/crypto.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/domexception.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/events.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/fetch.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/navigator.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/storage.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/streams.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/worker_threads.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/zlib.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/LICENSE +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/README.md +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/agent.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/api.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/balanced-pool.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/cache-interceptor.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/cache.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/client-stats.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/client.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/connector.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/content-type.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/cookies.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/diagnostics-channel.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/dispatcher.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/env-http-proxy-agent.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/errors.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/eventsource.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/fetch.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/formdata.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/global-dispatcher.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/global-origin.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/h2c-client.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/handlers.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/header.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/index.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/interceptors.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-agent.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-call-history.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-client.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-errors.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-interceptor.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-pool.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/package.json +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/patch.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/pool-stats.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/pool.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/proxy-agent.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/readable.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/retry-agent.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/retry-handler.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/round-robin-pool.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/snapshot-agent.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/util.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/utility.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/webidl.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/websocket.d.ts +0 -0
|
@@ -1,799 +0,0 @@
|
|
|
1
|
-
import { i as resolveLineAccount } from "./accounts-CSH5sk8U.js";
|
|
2
|
-
import { a as validateLineMediaUrl, t as createLineSendReceipt } from "./send-receipt-CY2ey8XH.js";
|
|
3
|
-
import { _ as postbackAction, g as normalizeLineMessageActions, h as normalizeLineAction, m as messageAction, o as createReceiptCard, v as uriAction } from "./flex-templates-DVy_wog4.js";
|
|
4
|
-
import { markdownToIRWithMeta, stripMarkdown, stripMarkdown as stripMarkdown$1 } from "openclaw/plugin-sdk/text-chunking";
|
|
5
|
-
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
6
|
-
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
7
|
-
import { messagingApi } from "@line/bot-sdk";
|
|
8
|
-
import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
|
|
9
|
-
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
10
|
-
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
11
|
-
//#region extensions/line/src/flex-templates/message.ts
|
|
12
|
-
/**
|
|
13
|
-
* Wrap a FlexContainer in a FlexMessage
|
|
14
|
-
*/
|
|
15
|
-
function toFlexMessage(altText, contents) {
|
|
16
|
-
return {
|
|
17
|
-
type: "flex",
|
|
18
|
-
altText,
|
|
19
|
-
contents
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
//#endregion
|
|
23
|
-
//#region extensions/line/src/template-messages.ts
|
|
24
|
-
const COMPACT_TEMPLATE_TEXT_LIMIT = 60;
|
|
25
|
-
const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
26
|
-
function buildTemplatePayloadAction(action) {
|
|
27
|
-
if (action.type === "uri" && action.uri) return uriAction(action.label, action.uri);
|
|
28
|
-
if (action.type === "postback" && action.data) return postbackAction(action.label, action.data, action.label);
|
|
29
|
-
return messageAction(action.label, action.data ?? action.label);
|
|
30
|
-
}
|
|
31
|
-
function resolveTemplateTextLimit(params) {
|
|
32
|
-
return params.title !== void 0 || params.thumbnailImageUrl !== void 0 ? COMPACT_TEMPLATE_TEXT_LIMIT : params.textOnlyLimit;
|
|
33
|
-
}
|
|
34
|
-
function truncateTemplateText(text, limit) {
|
|
35
|
-
let result = "";
|
|
36
|
-
for (const { segment } of graphemeSegmenter.segment(text)) {
|
|
37
|
-
if (result.length + segment.length > limit) {
|
|
38
|
-
if (!result) for (const codePoint of segment) {
|
|
39
|
-
if (result.length + codePoint.length > limit) break;
|
|
40
|
-
result += codePoint;
|
|
41
|
-
}
|
|
42
|
-
break;
|
|
43
|
-
}
|
|
44
|
-
result += segment;
|
|
45
|
-
}
|
|
46
|
-
return result;
|
|
47
|
-
}
|
|
48
|
-
function truncateOptionalTemplateText(value, limit) {
|
|
49
|
-
return value === void 0 ? void 0 : truncateTemplateText(value, limit);
|
|
50
|
-
}
|
|
51
|
-
function formatProductCarouselText(description, price) {
|
|
52
|
-
if (!price) return description;
|
|
53
|
-
const priceText = truncateTemplateText(price, COMPACT_TEMPLATE_TEXT_LIMIT);
|
|
54
|
-
const descriptionText = truncateTemplateText(description, Math.max(0, COMPACT_TEMPLATE_TEXT_LIMIT - priceText.length - 1));
|
|
55
|
-
return descriptionText ? `${descriptionText}\n${priceText}` : priceText;
|
|
56
|
-
}
|
|
57
|
-
function normalizeCarouselColumnActions(column) {
|
|
58
|
-
return {
|
|
59
|
-
...column,
|
|
60
|
-
actions: column.actions.map((action) => normalizeLineAction(action)),
|
|
61
|
-
defaultAction: column.defaultAction === void 0 ? void 0 : normalizeLineAction(column.defaultAction)
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
function normalizeImageCarouselColumnAction(column) {
|
|
65
|
-
return {
|
|
66
|
-
...column,
|
|
67
|
-
action: normalizeLineAction(column.action, 12)
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Create a confirm template (yes/no style dialog)
|
|
72
|
-
*/
|
|
73
|
-
function createConfirmTemplate(text, confirmAction, cancelAction, altText) {
|
|
74
|
-
const template = {
|
|
75
|
-
type: "confirm",
|
|
76
|
-
text: truncateTemplateText(text, 240),
|
|
77
|
-
actions: [normalizeLineAction(confirmAction), normalizeLineAction(cancelAction)]
|
|
78
|
-
};
|
|
79
|
-
return {
|
|
80
|
-
type: "template",
|
|
81
|
-
altText: truncateOptionalTemplateText(altText, 400) ?? truncateTemplateText(text, 400),
|
|
82
|
-
template
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Create a button template with title, text, and action buttons
|
|
87
|
-
*/
|
|
88
|
-
function createButtonTemplate(title, text, actions, options) {
|
|
89
|
-
const normalizedTitle = title || void 0;
|
|
90
|
-
const textLimit = resolveTemplateTextLimit({
|
|
91
|
-
title: normalizedTitle,
|
|
92
|
-
thumbnailImageUrl: options?.thumbnailImageUrl,
|
|
93
|
-
textOnlyLimit: 160
|
|
94
|
-
});
|
|
95
|
-
const template = {
|
|
96
|
-
type: "buttons",
|
|
97
|
-
...normalizedTitle ? { title: truncateTemplateText(normalizedTitle, 40) } : {},
|
|
98
|
-
text: truncateTemplateText(text, textLimit),
|
|
99
|
-
actions: actions.slice(0, 4).map((action) => normalizeLineAction(action)),
|
|
100
|
-
thumbnailImageUrl: options?.thumbnailImageUrl,
|
|
101
|
-
imageAspectRatio: options?.imageAspectRatio ?? "rectangle",
|
|
102
|
-
imageSize: options?.imageSize ?? "cover",
|
|
103
|
-
imageBackgroundColor: options?.imageBackgroundColor,
|
|
104
|
-
defaultAction: options?.defaultAction === void 0 ? void 0 : normalizeLineAction(options.defaultAction)
|
|
105
|
-
};
|
|
106
|
-
return {
|
|
107
|
-
type: "template",
|
|
108
|
-
altText: truncateOptionalTemplateText(options?.altText, 400) ?? truncateTemplateText(normalizedTitle ? `${normalizedTitle}: ${text}` : text, 400),
|
|
109
|
-
template
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Create a carousel template with multiple columns
|
|
114
|
-
*/
|
|
115
|
-
function createTemplateCarousel(columns, options) {
|
|
116
|
-
const template = {
|
|
117
|
-
type: "carousel",
|
|
118
|
-
columns: columns.slice(0, 10).map(normalizeCarouselColumnActions),
|
|
119
|
-
imageAspectRatio: options?.imageAspectRatio ?? "rectangle",
|
|
120
|
-
imageSize: options?.imageSize ?? "cover"
|
|
121
|
-
};
|
|
122
|
-
return {
|
|
123
|
-
type: "template",
|
|
124
|
-
altText: truncateOptionalTemplateText(options?.altText, 400) ?? "View carousel",
|
|
125
|
-
template
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Create a carousel column for use with createTemplateCarousel
|
|
130
|
-
*/
|
|
131
|
-
function createCarouselColumn(params) {
|
|
132
|
-
const textLimit = resolveTemplateTextLimit({
|
|
133
|
-
...params,
|
|
134
|
-
textOnlyLimit: 120
|
|
135
|
-
});
|
|
136
|
-
return {
|
|
137
|
-
title: truncateOptionalTemplateText(params.title, 40),
|
|
138
|
-
text: truncateTemplateText(params.text, textLimit),
|
|
139
|
-
actions: params.actions.slice(0, 3).map((action) => normalizeLineAction(action)),
|
|
140
|
-
thumbnailImageUrl: params.thumbnailImageUrl,
|
|
141
|
-
imageBackgroundColor: params.imageBackgroundColor,
|
|
142
|
-
defaultAction: params.defaultAction === void 0 ? void 0 : normalizeLineAction(params.defaultAction)
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Create an image carousel template (simpler, image-focused carousel)
|
|
147
|
-
*/
|
|
148
|
-
function createImageCarousel(columns, altText) {
|
|
149
|
-
const template = {
|
|
150
|
-
type: "image_carousel",
|
|
151
|
-
columns: columns.slice(0, 10).map(normalizeImageCarouselColumnAction)
|
|
152
|
-
};
|
|
153
|
-
return {
|
|
154
|
-
type: "template",
|
|
155
|
-
altText: truncateOptionalTemplateText(altText, 400) ?? "View images",
|
|
156
|
-
template
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Create an image carousel column for use with createImageCarousel
|
|
161
|
-
*/
|
|
162
|
-
function createImageCarouselColumn(imageUrl, action) {
|
|
163
|
-
return {
|
|
164
|
-
imageUrl,
|
|
165
|
-
action: normalizeLineAction(action, 12)
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Create a simple yes/no confirmation dialog
|
|
170
|
-
*/
|
|
171
|
-
function createYesNoConfirm(question, options) {
|
|
172
|
-
return createConfirmTemplate(question, options?.yesData ? postbackAction(options.yesText ?? "Yes", options.yesData, options.yesText ?? "Yes") : messageAction(options?.yesText ?? "Yes"), options?.noData ? postbackAction(options.noText ?? "No", options.noData, options.noText ?? "No") : messageAction(options?.noText ?? "No"), options?.altText);
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Create a button menu with simple text buttons
|
|
176
|
-
*/
|
|
177
|
-
function createButtonMenu(title, text, buttons, options) {
|
|
178
|
-
return createButtonTemplate(title, text, buttons.slice(0, 4).map((btn) => messageAction(btn.label, btn.text)), {
|
|
179
|
-
thumbnailImageUrl: options?.thumbnailImageUrl,
|
|
180
|
-
altText: options?.altText
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Create a button menu with URL links
|
|
185
|
-
*/
|
|
186
|
-
function createLinkMenu(title, text, links, options) {
|
|
187
|
-
return createButtonTemplate(title, text, links.slice(0, 4).map((link) => uriAction(link.label, link.url)), {
|
|
188
|
-
thumbnailImageUrl: options?.thumbnailImageUrl,
|
|
189
|
-
altText: options?.altText
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Create a simple product/item carousel
|
|
194
|
-
*/
|
|
195
|
-
function createProductCarousel(products, altText) {
|
|
196
|
-
return createTemplateCarousel(products.slice(0, 10).map((product) => {
|
|
197
|
-
const actions = [];
|
|
198
|
-
if (product.actionUrl) actions.push(uriAction(product.actionLabel ?? "View", product.actionUrl));
|
|
199
|
-
else if (product.actionData) actions.push(postbackAction(product.actionLabel ?? "Select", product.actionData));
|
|
200
|
-
else actions.push(messageAction(product.actionLabel ?? "Select", product.title));
|
|
201
|
-
return createCarouselColumn({
|
|
202
|
-
title: product.title,
|
|
203
|
-
text: formatProductCarouselText(product.description, product.price),
|
|
204
|
-
thumbnailImageUrl: product.imageUrl,
|
|
205
|
-
actions
|
|
206
|
-
});
|
|
207
|
-
}), { altText });
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Convert a TemplateMessagePayload from ReplyPayload to a LINE TemplateMessage
|
|
211
|
-
*/
|
|
212
|
-
function buildTemplateMessageFromPayload(payload) {
|
|
213
|
-
switch (payload.type) {
|
|
214
|
-
case "confirm": {
|
|
215
|
-
const confirmAction = payload.confirmData.startsWith("http") ? uriAction(payload.confirmLabel, payload.confirmData) : payload.confirmData.includes("=") ? postbackAction(payload.confirmLabel, payload.confirmData, payload.confirmLabel) : messageAction(payload.confirmLabel, payload.confirmData);
|
|
216
|
-
const cancelAction = payload.cancelData.startsWith("http") ? uriAction(payload.cancelLabel, payload.cancelData) : payload.cancelData.includes("=") ? postbackAction(payload.cancelLabel, payload.cancelData, payload.cancelLabel) : messageAction(payload.cancelLabel, payload.cancelData);
|
|
217
|
-
return createConfirmTemplate(payload.text, confirmAction, cancelAction, payload.altText);
|
|
218
|
-
}
|
|
219
|
-
case "buttons": {
|
|
220
|
-
const actions = payload.actions.slice(0, 4).map((action) => buildTemplatePayloadAction(action));
|
|
221
|
-
return createButtonTemplate(payload.title, payload.text, actions, {
|
|
222
|
-
thumbnailImageUrl: payload.thumbnailImageUrl,
|
|
223
|
-
altText: payload.altText
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
case "carousel": return createTemplateCarousel(payload.columns.slice(0, 10).map((col) => {
|
|
227
|
-
const colActions = col.actions.slice(0, 3).map((action) => buildTemplatePayloadAction(action));
|
|
228
|
-
return createCarouselColumn({
|
|
229
|
-
title: col.title,
|
|
230
|
-
text: col.text,
|
|
231
|
-
thumbnailImageUrl: col.thumbnailImageUrl,
|
|
232
|
-
actions: colActions
|
|
233
|
-
});
|
|
234
|
-
}), { altText: payload.altText });
|
|
235
|
-
default: return null;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
//#endregion
|
|
239
|
-
//#region extensions/line/src/channel-access-token.ts
|
|
240
|
-
function resolveLineChannelAccessToken(explicit, params) {
|
|
241
|
-
if (explicit?.trim()) return explicit.trim();
|
|
242
|
-
if (!params.channelAccessToken) throw new Error(`LINE channel access token missing for account "${params.accountId}" (set channels.line.channelAccessToken or LINE_CHANNEL_ACCESS_TOKEN).`);
|
|
243
|
-
return params.channelAccessToken.trim();
|
|
244
|
-
}
|
|
245
|
-
//#endregion
|
|
246
|
-
//#region extensions/line/src/send.ts
|
|
247
|
-
const userProfileCache = /* @__PURE__ */ new Map();
|
|
248
|
-
const PROFILE_CACHE_TTL_MS = 300 * 1e3;
|
|
249
|
-
const PROFILE_CACHE_MAX_ENTRIES = 1e3;
|
|
250
|
-
function cacheUserProfile(userId, profile) {
|
|
251
|
-
userProfileCache.delete(userId);
|
|
252
|
-
userProfileCache.set(userId, profile);
|
|
253
|
-
if (userProfileCache.size <= PROFILE_CACHE_MAX_ENTRIES) return;
|
|
254
|
-
for (const [key, cached] of userProfileCache) if (profile.fetchedAt - cached.fetchedAt >= PROFILE_CACHE_TTL_MS) userProfileCache.delete(key);
|
|
255
|
-
pruneMapToMaxSize(userProfileCache, PROFILE_CACHE_MAX_ENTRIES);
|
|
256
|
-
}
|
|
257
|
-
function normalizeTarget(to) {
|
|
258
|
-
const trimmed = to.trim();
|
|
259
|
-
if (!trimmed) throw new Error("Recipient is required for LINE sends");
|
|
260
|
-
const normalized = trimmed.replace(/^line:group:/i, "").replace(/^line:room:/i, "").replace(/^line:user:/i, "").replace(/^line:/i, "");
|
|
261
|
-
if (!normalized) throw new Error("Recipient is required for LINE sends");
|
|
262
|
-
if (normalized.length >= 33 && !/^[CUR]/.test(normalized)) throw new Error(`Recipient is not a valid LINE id (case-sensitive; expected leading capital C/U/R): ${truncateUtf16Safe(normalized, 4)}…`);
|
|
263
|
-
return normalized;
|
|
264
|
-
}
|
|
265
|
-
function isLineUserChatId(chatId) {
|
|
266
|
-
return /^U/i.test(chatId);
|
|
267
|
-
}
|
|
268
|
-
function createLineMessagingClient(opts) {
|
|
269
|
-
const account = resolveLineAccount({
|
|
270
|
-
cfg: requireRuntimeConfig(opts.cfg, "LINE send"),
|
|
271
|
-
accountId: opts.accountId
|
|
272
|
-
});
|
|
273
|
-
const token = resolveLineChannelAccessToken(opts.channelAccessToken, account);
|
|
274
|
-
return {
|
|
275
|
-
account,
|
|
276
|
-
client: new messagingApi.MessagingApiClient({ channelAccessToken: token })
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
function createLinePushContext(to, opts) {
|
|
280
|
-
const { account, client } = createLineMessagingClient(opts);
|
|
281
|
-
return {
|
|
282
|
-
account,
|
|
283
|
-
client,
|
|
284
|
-
chatId: normalizeTarget(to)
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
function createTextMessage(text) {
|
|
288
|
-
return {
|
|
289
|
-
type: "text",
|
|
290
|
-
text
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
function createImageMessage(originalContentUrl, previewImageUrl) {
|
|
294
|
-
return {
|
|
295
|
-
type: "image",
|
|
296
|
-
originalContentUrl,
|
|
297
|
-
previewImageUrl: previewImageUrl ?? originalContentUrl
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
function createVideoMessage(originalContentUrl, previewImageUrl, trackingId) {
|
|
301
|
-
return {
|
|
302
|
-
type: "video",
|
|
303
|
-
originalContentUrl,
|
|
304
|
-
previewImageUrl,
|
|
305
|
-
...trackingId ? { trackingId } : {}
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
function createAudioMessage(originalContentUrl, durationMs) {
|
|
309
|
-
return {
|
|
310
|
-
type: "audio",
|
|
311
|
-
originalContentUrl,
|
|
312
|
-
duration: durationMs
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
function createLocationMessage(location) {
|
|
316
|
-
return {
|
|
317
|
-
type: "location",
|
|
318
|
-
title: truncateUtf16Safe(location.title, 100),
|
|
319
|
-
address: truncateUtf16Safe(location.address, 100),
|
|
320
|
-
latitude: location.latitude,
|
|
321
|
-
longitude: location.longitude
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
function logLineHttpError(err, context) {
|
|
325
|
-
if (!err || typeof err !== "object") return;
|
|
326
|
-
const { status, statusText, body } = err;
|
|
327
|
-
if (typeof body === "string") logVerbose(`line: ${context} failed (${status ? `${status} ${statusText ?? ""}`.trim() : "unknown status"}): ${body}`);
|
|
328
|
-
}
|
|
329
|
-
function recordLineOutboundActivity(accountId) {
|
|
330
|
-
recordChannelActivity({
|
|
331
|
-
channel: "line",
|
|
332
|
-
accountId,
|
|
333
|
-
direction: "outbound"
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
function resolveLineReceiptKind(messages) {
|
|
337
|
-
const types = new Set(messages.map((message) => message.type));
|
|
338
|
-
if (types.has("audio")) return "voice";
|
|
339
|
-
if (types.has("image") || types.has("video")) return "media";
|
|
340
|
-
if (types.has("flex") || types.has("template") || types.has("location")) return "card";
|
|
341
|
-
if (types.has("text")) return "text";
|
|
342
|
-
return "unknown";
|
|
343
|
-
}
|
|
344
|
-
async function pushLineMessages(to, messages, opts, behavior = {}) {
|
|
345
|
-
if (messages.length === 0) throw new Error("Message must be non-empty for LINE sends");
|
|
346
|
-
const { account, client, chatId } = createLinePushContext(to, opts);
|
|
347
|
-
const normalizedMessages = messages.map(normalizeLineMessageActions);
|
|
348
|
-
const pushRequest = client.pushMessage({
|
|
349
|
-
to: chatId,
|
|
350
|
-
messages: normalizedMessages
|
|
351
|
-
});
|
|
352
|
-
if (behavior.errorContext) await pushRequest.catch((err) => {
|
|
353
|
-
logLineHttpError(err, behavior.errorContext);
|
|
354
|
-
throw err;
|
|
355
|
-
});
|
|
356
|
-
else await pushRequest;
|
|
357
|
-
recordLineOutboundActivity(account.accountId);
|
|
358
|
-
if (opts.verbose) logVerbose(behavior.verboseMessage?.(chatId, messages.length) ?? `line: pushed ${messages.length} messages to ${chatId}`);
|
|
359
|
-
return {
|
|
360
|
-
messageId: "push",
|
|
361
|
-
chatId,
|
|
362
|
-
receipt: createLineSendReceipt({
|
|
363
|
-
messageId: "push",
|
|
364
|
-
chatId,
|
|
365
|
-
kind: resolveLineReceiptKind(messages),
|
|
366
|
-
messageCount: messages.length
|
|
367
|
-
})
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
async function replyLineMessages(replyToken, messages, opts, behavior = {}) {
|
|
371
|
-
const { account, client } = createLineMessagingClient(opts);
|
|
372
|
-
const normalizedMessages = messages.map(normalizeLineMessageActions);
|
|
373
|
-
await client.replyMessage({
|
|
374
|
-
replyToken,
|
|
375
|
-
messages: normalizedMessages
|
|
376
|
-
});
|
|
377
|
-
recordLineOutboundActivity(account.accountId);
|
|
378
|
-
if (opts.verbose) logVerbose(behavior.verboseMessage?.(messages.length) ?? `line: replied with ${messages.length} messages`);
|
|
379
|
-
}
|
|
380
|
-
async function sendMessageLine(to, text, opts) {
|
|
381
|
-
const chatId = normalizeTarget(to);
|
|
382
|
-
const messages = [];
|
|
383
|
-
const mediaUrl = opts.mediaUrl?.trim();
|
|
384
|
-
if (mediaUrl) {
|
|
385
|
-
await validateLineMediaUrl(mediaUrl);
|
|
386
|
-
switch (opts.mediaKind) {
|
|
387
|
-
case "video": {
|
|
388
|
-
const previewImageUrl = opts.previewImageUrl?.trim();
|
|
389
|
-
if (!previewImageUrl) throw new Error("LINE video messages require previewImageUrl to reference an image URL");
|
|
390
|
-
await validateLineMediaUrl(previewImageUrl);
|
|
391
|
-
const trackingId = isLineUserChatId(chatId) ? opts.trackingId : void 0;
|
|
392
|
-
messages.push(createVideoMessage(mediaUrl, previewImageUrl, trackingId));
|
|
393
|
-
break;
|
|
394
|
-
}
|
|
395
|
-
case "audio":
|
|
396
|
-
messages.push(createAudioMessage(mediaUrl, opts.durationMs ?? 6e4));
|
|
397
|
-
break;
|
|
398
|
-
default:
|
|
399
|
-
{
|
|
400
|
-
const previewImageUrl = opts.previewImageUrl?.trim() || mediaUrl;
|
|
401
|
-
await validateLineMediaUrl(previewImageUrl);
|
|
402
|
-
messages.push(createImageMessage(mediaUrl, previewImageUrl));
|
|
403
|
-
}
|
|
404
|
-
break;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
if (text?.trim()) messages.push(createTextMessage(text.trim()));
|
|
408
|
-
if (messages.length === 0) throw new Error("Message must be non-empty for LINE sends");
|
|
409
|
-
if (opts.replyToken) {
|
|
410
|
-
await replyLineMessages(opts.replyToken, messages, opts, { verboseMessage: () => `line: replied to ${chatId}` });
|
|
411
|
-
return {
|
|
412
|
-
messageId: "reply",
|
|
413
|
-
chatId,
|
|
414
|
-
receipt: createLineSendReceipt({
|
|
415
|
-
messageId: "reply",
|
|
416
|
-
chatId,
|
|
417
|
-
kind: resolveLineReceiptKind(messages),
|
|
418
|
-
messageCount: messages.length
|
|
419
|
-
})
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
return pushLineMessages(chatId, messages, opts, { verboseMessage: (resolvedChatId) => `line: pushed message to ${resolvedChatId}` });
|
|
423
|
-
}
|
|
424
|
-
async function pushMessageLine(to, text, opts) {
|
|
425
|
-
return sendMessageLine(to, text, {
|
|
426
|
-
...opts,
|
|
427
|
-
replyToken: void 0
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
async function replyMessageLine(replyToken, messages, opts) {
|
|
431
|
-
await replyLineMessages(replyToken, messages, opts);
|
|
432
|
-
}
|
|
433
|
-
async function pushMessagesLine(to, messages, opts) {
|
|
434
|
-
return pushLineMessages(to, messages, opts, { errorContext: "push message" });
|
|
435
|
-
}
|
|
436
|
-
function createFlexMessage(altText, contents) {
|
|
437
|
-
return {
|
|
438
|
-
type: "flex",
|
|
439
|
-
altText,
|
|
440
|
-
contents
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
async function pushImageMessage(to, originalContentUrl, previewImageUrl, opts) {
|
|
444
|
-
await validateLineMediaUrl(originalContentUrl);
|
|
445
|
-
if (previewImageUrl) await validateLineMediaUrl(previewImageUrl);
|
|
446
|
-
return pushLineMessages(to, [createImageMessage(originalContentUrl, previewImageUrl)], opts, { verboseMessage: (chatId) => `line: pushed image to ${chatId}` });
|
|
447
|
-
}
|
|
448
|
-
async function pushLocationMessage(to, location, opts) {
|
|
449
|
-
return pushLineMessages(to, [createLocationMessage(location)], opts, { verboseMessage: (chatId) => `line: pushed location to ${chatId}` });
|
|
450
|
-
}
|
|
451
|
-
async function pushFlexMessage(to, altText, contents, opts) {
|
|
452
|
-
return pushLineMessages(to, [{
|
|
453
|
-
type: "flex",
|
|
454
|
-
altText: truncateUtf16Safe(altText, 400),
|
|
455
|
-
contents
|
|
456
|
-
}], opts, {
|
|
457
|
-
errorContext: "push flex message",
|
|
458
|
-
verboseMessage: (chatId) => `line: pushed flex message to ${chatId}`
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
async function pushTemplateMessage(to, template, opts) {
|
|
462
|
-
return pushLineMessages(to, [template], opts, { verboseMessage: (chatId) => `line: pushed template message to ${chatId}` });
|
|
463
|
-
}
|
|
464
|
-
async function pushTextMessageWithQuickReplies(to, text, quickReplyLabels, opts) {
|
|
465
|
-
return pushLineMessages(to, [createTextMessageWithQuickReplies(text, quickReplyLabels)], opts, { verboseMessage: (chatId) => `line: pushed message with quick replies to ${chatId}` });
|
|
466
|
-
}
|
|
467
|
-
function createQuickReplyItems(labels) {
|
|
468
|
-
return { items: labels.slice(0, 13).map((label) => ({
|
|
469
|
-
type: "action",
|
|
470
|
-
action: messageAction(label, label)
|
|
471
|
-
})) };
|
|
472
|
-
}
|
|
473
|
-
function createTextMessageWithQuickReplies(text, quickReplyLabels) {
|
|
474
|
-
return {
|
|
475
|
-
type: "text",
|
|
476
|
-
text,
|
|
477
|
-
quickReply: createQuickReplyItems(quickReplyLabels)
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
async function showLoadingAnimation(chatId, opts) {
|
|
481
|
-
const { client } = createLineMessagingClient(opts);
|
|
482
|
-
try {
|
|
483
|
-
await client.showLoadingAnimation({
|
|
484
|
-
chatId: normalizeTarget(chatId),
|
|
485
|
-
loadingSeconds: opts.loadingSeconds ?? 20
|
|
486
|
-
});
|
|
487
|
-
logVerbose(`line: showing loading animation to ${chatId}`);
|
|
488
|
-
} catch (err) {
|
|
489
|
-
logVerbose(`line: loading animation failed (non-fatal): ${String(err)}`);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
async function getUserProfile(userId, opts) {
|
|
493
|
-
if (opts.useCache ?? true) {
|
|
494
|
-
const cached = userProfileCache.get(userId);
|
|
495
|
-
if (cached && Date.now() - cached.fetchedAt < PROFILE_CACHE_TTL_MS) return {
|
|
496
|
-
displayName: cached.displayName,
|
|
497
|
-
pictureUrl: cached.pictureUrl
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
const { client } = createLineMessagingClient(opts);
|
|
501
|
-
try {
|
|
502
|
-
const profile = await client.getProfile(userId);
|
|
503
|
-
const result = {
|
|
504
|
-
displayName: profile.displayName,
|
|
505
|
-
pictureUrl: profile.pictureUrl
|
|
506
|
-
};
|
|
507
|
-
cacheUserProfile(userId, {
|
|
508
|
-
...result,
|
|
509
|
-
fetchedAt: Date.now()
|
|
510
|
-
});
|
|
511
|
-
return result;
|
|
512
|
-
} catch (err) {
|
|
513
|
-
logVerbose(`line: failed to fetch profile for ${userId}: ${String(err)}`);
|
|
514
|
-
return null;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
async function getUserDisplayName(userId, opts) {
|
|
518
|
-
return (await getUserProfile(userId, opts))?.displayName ?? userId;
|
|
519
|
-
}
|
|
520
|
-
//#endregion
|
|
521
|
-
//#region extensions/line/src/markdown-to-line.ts
|
|
522
|
-
const LINE_MARKDOWN_OPTIONS = {
|
|
523
|
-
assistantTranscriptRoleHeaders: true,
|
|
524
|
-
autolink: false,
|
|
525
|
-
blockquotePrefix: "",
|
|
526
|
-
headingStyle: "none",
|
|
527
|
-
horizontalRuleText: "",
|
|
528
|
-
linkify: false,
|
|
529
|
-
preserveSourceBlockSpacing: true
|
|
530
|
-
};
|
|
531
|
-
const TRANSCRIPT_ROLE_PREFIX = "[assistant-authored transcript] ";
|
|
532
|
-
function parseLineMarkdown(text, tableMode = "block") {
|
|
533
|
-
return markdownToIRWithMeta(text, {
|
|
534
|
-
...LINE_MARKDOWN_OPTIONS,
|
|
535
|
-
tableMode
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
|
-
function toMarkdownTable(table) {
|
|
539
|
-
return {
|
|
540
|
-
headers: table.headers,
|
|
541
|
-
rows: table.rows,
|
|
542
|
-
headerCells: table.headerCells,
|
|
543
|
-
rowCells: table.rowCells
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
function codeBlockSpans(ir) {
|
|
547
|
-
return ir.styles.filter((span) => span.style === "code_block");
|
|
548
|
-
}
|
|
549
|
-
function toCodeBlock(ir, span) {
|
|
550
|
-
return {
|
|
551
|
-
...span.language ? { language: span.language } : {},
|
|
552
|
-
code: ir.text.slice(span.start, span.end).trim()
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
function rangesOverlap(left, right) {
|
|
556
|
-
return left.start < right.end && right.start < left.end;
|
|
557
|
-
}
|
|
558
|
-
function projectPlainText(ir, omitted = []) {
|
|
559
|
-
const insertions = [];
|
|
560
|
-
for (const link of ir.links) {
|
|
561
|
-
if (omitted.some((range) => rangesOverlap(range, link))) continue;
|
|
562
|
-
const href = link.href.trim();
|
|
563
|
-
const label = ir.text.slice(link.start, link.end).trim();
|
|
564
|
-
const comparableHref = href.startsWith("mailto:") ? href.slice(7) : href;
|
|
565
|
-
if (href && label && label !== href && label !== comparableHref) insertions.push({
|
|
566
|
-
position: link.end,
|
|
567
|
-
text: ` (${href})`
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
for (const annotation of ir.annotations ?? []) if (annotation.type === "assistant_transcript_role" && !omitted.some((range) => rangesOverlap(range, annotation))) insertions.push({
|
|
571
|
-
position: annotation.start,
|
|
572
|
-
text: TRANSCRIPT_ROLE_PREFIX
|
|
573
|
-
});
|
|
574
|
-
const inlineCodeSpans = ir.styles.filter((span) => span.style === "code" && !omitted.some((range) => rangesOverlap(range, span)));
|
|
575
|
-
for (const span of inlineCodeSpans) if (stripMarkdown(ir.text.slice(span.start, span.end), { assistantTranscriptRoleHeaders: true }).startsWith(TRANSCRIPT_ROLE_PREFIX)) insertions.push({
|
|
576
|
-
position: span.start,
|
|
577
|
-
text: TRANSCRIPT_ROLE_PREFIX
|
|
578
|
-
});
|
|
579
|
-
insertions.sort((left, right) => left.position - right.position);
|
|
580
|
-
const underlineTags = [...ir.text.matchAll(/<\/?u>/gi)].map((match) => ({
|
|
581
|
-
start: match.index,
|
|
582
|
-
end: match.index + match[0].length
|
|
583
|
-
})).filter((tag) => !inlineCodeSpans.some((span) => rangesOverlap(tag, span)) && !omitted.some((span) => rangesOverlap(tag, span)));
|
|
584
|
-
const removed = [...omitted, ...underlineTags].toSorted((left, right) => left.start - right.start);
|
|
585
|
-
let output = "";
|
|
586
|
-
let cursor = 0;
|
|
587
|
-
let insertionIndex = 0;
|
|
588
|
-
const appendRange = (end) => {
|
|
589
|
-
while (insertionIndex < insertions.length) {
|
|
590
|
-
const insertion = insertions[insertionIndex];
|
|
591
|
-
if (!insertion || insertion.position > end) break;
|
|
592
|
-
if (insertion.position >= cursor) {
|
|
593
|
-
output += ir.text.slice(cursor, insertion.position) + insertion.text;
|
|
594
|
-
cursor = insertion.position;
|
|
595
|
-
}
|
|
596
|
-
insertionIndex += 1;
|
|
597
|
-
}
|
|
598
|
-
output += ir.text.slice(cursor, end);
|
|
599
|
-
cursor = end;
|
|
600
|
-
};
|
|
601
|
-
for (const range of removed) {
|
|
602
|
-
appendRange(range.start);
|
|
603
|
-
cursor = Math.max(cursor, range.end);
|
|
604
|
-
while (insertionIndex < insertions.length && (insertions[insertionIndex]?.position ?? cursor) < cursor) insertionIndex += 1;
|
|
605
|
-
}
|
|
606
|
-
appendRange(ir.text.length);
|
|
607
|
-
return output.trim();
|
|
608
|
-
}
|
|
609
|
-
function sameSpanStyle(left, right) {
|
|
610
|
-
return left.weight === right.weight && left.style === right.style && left.decoration === right.decoration;
|
|
611
|
-
}
|
|
612
|
-
function renderTableCell(cell, fallback) {
|
|
613
|
-
if (!cell?.text.trim()) return {
|
|
614
|
-
text: fallback,
|
|
615
|
-
hasMarkup: false
|
|
616
|
-
};
|
|
617
|
-
const codeSpans = cell.styles.filter((span) => span.style === "code");
|
|
618
|
-
const tags = [...cell.text.matchAll(/<\/?u>/gi)].map((match) => ({
|
|
619
|
-
start: match.index,
|
|
620
|
-
end: match.index + match[0].length,
|
|
621
|
-
closing: match[0][1] === "/"
|
|
622
|
-
})).filter((tag) => !codeSpans.some((span) => rangesOverlap(tag, span)));
|
|
623
|
-
const boundaries = /* @__PURE__ */ new Set([0, cell.text.length]);
|
|
624
|
-
for (const style of cell.styles) {
|
|
625
|
-
boundaries.add(style.start);
|
|
626
|
-
boundaries.add(style.end);
|
|
627
|
-
}
|
|
628
|
-
for (const link of cell.links) boundaries.add(link.end);
|
|
629
|
-
for (const tag of tags) {
|
|
630
|
-
boundaries.add(tag.start);
|
|
631
|
-
boundaries.add(tag.end);
|
|
632
|
-
}
|
|
633
|
-
const sortedBoundaries = [...boundaries].toSorted((left, right) => left - right);
|
|
634
|
-
const spans = [];
|
|
635
|
-
let underlineDepth = 0;
|
|
636
|
-
let hasMarkup = false;
|
|
637
|
-
const appendSpan = (span) => {
|
|
638
|
-
const previous = spans.at(-1);
|
|
639
|
-
if (previous && sameSpanStyle(previous, span)) previous.text = `${previous.text ?? ""}${span.text ?? ""}`;
|
|
640
|
-
else spans.push(span);
|
|
641
|
-
};
|
|
642
|
-
for (let index = 0; index < sortedBoundaries.length - 1; index += 1) {
|
|
643
|
-
const start = sortedBoundaries[index];
|
|
644
|
-
const end = sortedBoundaries[index + 1];
|
|
645
|
-
if (start === void 0 || end === void 0) continue;
|
|
646
|
-
const tag = tags.find((candidate) => candidate.start === start);
|
|
647
|
-
if (tag) {
|
|
648
|
-
underlineDepth += tag.closing ? -1 : 1;
|
|
649
|
-
hasMarkup = true;
|
|
650
|
-
continue;
|
|
651
|
-
}
|
|
652
|
-
const text = cell.text.slice(start, end);
|
|
653
|
-
if (text) {
|
|
654
|
-
const active = new Set(cell.styles.filter((style) => style.start <= start && style.end >= end).map((style) => style.style));
|
|
655
|
-
const weight = active.has("bold") ? "bold" : void 0;
|
|
656
|
-
const style = active.has("italic") ? "italic" : void 0;
|
|
657
|
-
const decoration = active.has("strikethrough") ? "line-through" : underlineDepth > 0 ? "underline" : void 0;
|
|
658
|
-
hasMarkup ||= weight !== void 0 || style !== void 0 || decoration !== void 0;
|
|
659
|
-
appendSpan({
|
|
660
|
-
type: "span",
|
|
661
|
-
text,
|
|
662
|
-
weight,
|
|
663
|
-
style,
|
|
664
|
-
decoration
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
for (const link of cell.links.filter((candidate) => candidate.end === end)) {
|
|
668
|
-
const href = link.href.trim();
|
|
669
|
-
const label = cell.text.slice(link.start, link.end).trim();
|
|
670
|
-
if (href && label && label !== href) {
|
|
671
|
-
appendSpan({
|
|
672
|
-
type: "span",
|
|
673
|
-
text: ` (${href})`
|
|
674
|
-
});
|
|
675
|
-
hasMarkup = true;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
return {
|
|
680
|
-
text: spans.map((span) => span.text ?? "").join("").trim() || fallback,
|
|
681
|
-
...hasMarkup ? { contents: spans } : {},
|
|
682
|
-
hasMarkup
|
|
683
|
-
};
|
|
684
|
-
}
|
|
685
|
-
function plainTableCell(text) {
|
|
686
|
-
return parseLineMarkdown(text, "off").ir;
|
|
687
|
-
}
|
|
688
|
-
/** Convert a markdown table to a LINE Flex Message bubble. */
|
|
689
|
-
function convertTableToFlexBubble(table) {
|
|
690
|
-
const headerCells = (table.headerCells ?? table.headers.map(plainTableCell)).map((cell) => renderTableCell(cell, "-"));
|
|
691
|
-
const rowCells = (table.rowCells ?? table.rows.map((row) => row.map(plainTableCell))).map((row) => row.map((cell) => renderTableCell(cell, "-")));
|
|
692
|
-
const hasInlineMarkup = headerCells.some((cell) => cell.hasMarkup) || rowCells.some((row) => row.some((cell) => cell.hasMarkup));
|
|
693
|
-
if (table.headers.length === 2 && !hasInlineMarkup) return createReceiptCard({
|
|
694
|
-
title: headerCells.map((cell) => cell.text).join(" / "),
|
|
695
|
-
items: rowCells.map((row) => ({
|
|
696
|
-
name: row[0]?.text ?? "-",
|
|
697
|
-
value: row[1]?.text ?? "-"
|
|
698
|
-
}))
|
|
699
|
-
});
|
|
700
|
-
return {
|
|
701
|
-
type: "bubble",
|
|
702
|
-
body: {
|
|
703
|
-
type: "box",
|
|
704
|
-
layout: "vertical",
|
|
705
|
-
contents: [
|
|
706
|
-
{
|
|
707
|
-
type: "box",
|
|
708
|
-
layout: "horizontal",
|
|
709
|
-
contents: headerCells.map((cell) => ({
|
|
710
|
-
type: "text",
|
|
711
|
-
text: cell.text,
|
|
712
|
-
contents: cell.contents,
|
|
713
|
-
weight: "bold",
|
|
714
|
-
size: "sm",
|
|
715
|
-
color: "#333333",
|
|
716
|
-
flex: 1,
|
|
717
|
-
wrap: true
|
|
718
|
-
})),
|
|
719
|
-
paddingBottom: "sm"
|
|
720
|
-
},
|
|
721
|
-
{
|
|
722
|
-
type: "separator",
|
|
723
|
-
margin: "sm"
|
|
724
|
-
},
|
|
725
|
-
...rowCells.slice(0, 10).map((row, rowIndex) => ({
|
|
726
|
-
type: "box",
|
|
727
|
-
layout: "horizontal",
|
|
728
|
-
contents: table.headers.map((_, colIndex) => {
|
|
729
|
-
const cell = row[colIndex] ?? {
|
|
730
|
-
text: "-",
|
|
731
|
-
hasMarkup: false
|
|
732
|
-
};
|
|
733
|
-
return {
|
|
734
|
-
type: "text",
|
|
735
|
-
text: cell.text,
|
|
736
|
-
contents: cell.contents,
|
|
737
|
-
size: "sm",
|
|
738
|
-
color: "#666666",
|
|
739
|
-
flex: 1,
|
|
740
|
-
wrap: true
|
|
741
|
-
};
|
|
742
|
-
}),
|
|
743
|
-
margin: rowIndex === 0 ? "md" : "sm"
|
|
744
|
-
}))
|
|
745
|
-
],
|
|
746
|
-
paddingAll: "lg"
|
|
747
|
-
}
|
|
748
|
-
};
|
|
749
|
-
}
|
|
750
|
-
/** Convert a code block to a LINE Flex Message bubble. */
|
|
751
|
-
function convertCodeBlockToFlexBubble(block) {
|
|
752
|
-
const titleText = block.language ? `Code (${block.language})` : "Code";
|
|
753
|
-
const displayCode = block.code.length > 2e3 ? truncateUtf16Safe(block.code, 2e3) + "\n..." : block.code;
|
|
754
|
-
return {
|
|
755
|
-
type: "bubble",
|
|
756
|
-
body: {
|
|
757
|
-
type: "box",
|
|
758
|
-
layout: "vertical",
|
|
759
|
-
contents: [{
|
|
760
|
-
type: "text",
|
|
761
|
-
text: titleText,
|
|
762
|
-
weight: "bold",
|
|
763
|
-
size: "sm",
|
|
764
|
-
color: "#666666"
|
|
765
|
-
}, {
|
|
766
|
-
type: "box",
|
|
767
|
-
layout: "vertical",
|
|
768
|
-
contents: [{
|
|
769
|
-
type: "text",
|
|
770
|
-
text: displayCode,
|
|
771
|
-
size: "xs",
|
|
772
|
-
color: "#333333",
|
|
773
|
-
wrap: true
|
|
774
|
-
}],
|
|
775
|
-
backgroundColor: "#F5F5F5",
|
|
776
|
-
paddingAll: "md",
|
|
777
|
-
cornerRadius: "md",
|
|
778
|
-
margin: "sm"
|
|
779
|
-
}],
|
|
780
|
-
paddingAll: "lg"
|
|
781
|
-
}
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
/** Parse once, route existing block surfaces to Flex, and project the remainder as plain text. */
|
|
785
|
-
function processLineMessage(text) {
|
|
786
|
-
const { ir, tables } = parseLineMarkdown(text);
|
|
787
|
-
const codeSpans = codeBlockSpans(ir);
|
|
788
|
-
return {
|
|
789
|
-
text: projectPlainText(ir, codeSpans),
|
|
790
|
-
flexMessages: [...tables.map((table) => toFlexMessage("Table", convertTableToFlexBubble(toMarkdownTable(table)))), ...codeSpans.map((span) => toFlexMessage("Code", convertCodeBlockToFlexBubble(toCodeBlock(ir, span))))]
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
/** Check if text contains markdown that needs conversion. */
|
|
794
|
-
function hasMarkdownToConvert(text) {
|
|
795
|
-
const { ir, tables } = parseLineMarkdown(text);
|
|
796
|
-
return tables.length > 0 || ir.styles.length > 0 || ir.links.length > 0 || /<\/?u>/i.test(ir.text) || ir.text !== text.trimEnd();
|
|
797
|
-
}
|
|
798
|
-
//#endregion
|
|
799
|
-
export { createConfirmTemplate as A, sendMessageLine as C, createButtonMenu as D, buildTemplateMessageFromPayload as E, createTemplateCarousel as F, createYesNoConfirm as I, toFlexMessage as L, createImageCarouselColumn as M, createLinkMenu as N, createButtonTemplate as O, createProductCarousel as P, replyMessageLine as S, resolveLineChannelAccessToken as T, pushLocationMessage as _, stripMarkdown$1 as a, pushTemplateMessage as b, createImageMessage as c, createTextMessageWithQuickReplies as d, createVideoMessage as f, pushImageMessage as g, pushFlexMessage as h, processLineMessage as i, createImageCarousel as j, createCarouselColumn as k, createLocationMessage as l, getUserProfile as m, convertTableToFlexBubble as n, createAudioMessage as o, getUserDisplayName as p, hasMarkdownToConvert as r, createFlexMessage as s, convertCodeBlockToFlexBubble as t, createQuickReplyItems as u, pushMessageLine as v, showLoadingAnimation as w, pushTextMessageWithQuickReplies as x, pushMessagesLine as y };
|