@kodelyth/line 2026.5.39 → 2026.5.42
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/api.ts +11 -0
- package/channel-plugin-api.ts +1 -0
- package/contract-api.ts +5 -0
- package/dist/accounts-CD4A1FE7.js +105 -0
- package/dist/api.js +11 -0
- package/dist/basic-cards-BISytiSa.js +307 -0
- package/dist/card-command-dQBX3fVN.js +240 -0
- package/dist/channel-DV5h44-j.js +649 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.runtime-Cc-v3szZ.js +4 -0
- package/dist/contract-api.js +2 -0
- package/dist/index.js +45 -0
- package/dist/markdown-to-line-CC3BU6CC.js +810 -0
- package/dist/monitor-Ci8Hg8ay.js +1485 -0
- package/dist/monitor.runtime-t6-QvlDB.js +2 -0
- package/dist/outbound.runtime-D1CxEvcL.js +2 -0
- package/dist/probe-BPSs_A_8.js +30 -0
- package/dist/probe.runtime-7u2o9QN5.js +2 -0
- package/dist/reply-payload-transform-CDuBzoT4.js +855 -0
- package/dist/runtime-api.js +291 -0
- package/dist/schedule-cards-D-yZMHDE.js +359 -0
- package/dist/secret-contract-api.js +5 -0
- package/dist/setup-api.js +2 -0
- package/dist/setup-entry.js +11 -0
- package/dist/setup-surface-CHfQ6Z4i.js +282 -0
- package/index.ts +53 -0
- package/klaw.plugin.json +2 -329
- package/package.json +4 -4
- package/runtime-api.ts +179 -0
- package/secret-contract-api.ts +4 -0
- package/setup-api.ts +2 -0
- package/setup-entry.ts +9 -0
- package/src/account-helpers.ts +16 -0
- package/src/accounts.test.ts +288 -0
- package/src/accounts.ts +187 -0
- package/src/actions.ts +61 -0
- package/src/auto-reply-delivery.test.ts +253 -0
- package/src/auto-reply-delivery.ts +200 -0
- package/src/bindings.ts +65 -0
- package/src/bot-access.ts +30 -0
- package/src/bot-handlers.test.ts +1094 -0
- package/src/bot-handlers.ts +620 -0
- package/src/bot-message-context.test.ts +420 -0
- package/src/bot-message-context.ts +586 -0
- package/src/bot.ts +66 -0
- package/src/card-command.ts +347 -0
- package/src/channel-access-token.ts +14 -0
- package/src/channel-api.ts +17 -0
- package/src/channel-setup-status.contract.test.ts +70 -0
- package/src/channel-shared.ts +48 -0
- package/src/channel.logout.test.ts +145 -0
- package/src/channel.runtime.ts +3 -0
- package/src/channel.sendPayload.test.ts +659 -0
- package/src/channel.setup.ts +11 -0
- package/src/channel.status.test.ts +63 -0
- package/src/channel.ts +155 -0
- package/src/config-adapter.ts +29 -0
- package/src/config-schema.test.ts +53 -0
- package/src/config-schema.ts +81 -0
- package/src/download.test.ts +164 -0
- package/src/download.ts +34 -0
- package/src/flex-templates/basic-cards.ts +395 -0
- package/src/flex-templates/common.ts +20 -0
- package/src/flex-templates/media-control-cards.ts +555 -0
- package/src/flex-templates/message.ts +13 -0
- package/src/flex-templates/schedule-cards.ts +467 -0
- package/src/flex-templates/types.ts +22 -0
- package/src/flex-templates.ts +32 -0
- package/src/gateway.ts +129 -0
- package/src/group-keys.test.ts +123 -0
- package/src/group-keys.ts +65 -0
- package/src/group-policy.ts +22 -0
- package/src/markdown-to-line.test.ts +348 -0
- package/src/markdown-to-line.ts +416 -0
- package/src/message-cards.test.ts +204 -0
- package/src/monitor-durable.test.ts +57 -0
- package/src/monitor-durable.ts +37 -0
- package/src/monitor.lifecycle.test.ts +499 -0
- package/src/monitor.runtime.ts +1 -0
- package/src/monitor.ts +507 -0
- package/src/outbound-media.test.ts +194 -0
- package/src/outbound-media.ts +120 -0
- package/src/outbound.runtime.ts +12 -0
- package/src/outbound.ts +427 -0
- package/src/probe.contract.test.ts +9 -0
- package/src/probe.runtime.ts +1 -0
- package/src/probe.ts +34 -0
- package/src/quick-reply-fallback.ts +10 -0
- package/src/reply-chunks.test.ts +180 -0
- package/src/reply-chunks.ts +110 -0
- package/src/reply-payload-transform.test.ts +392 -0
- package/src/reply-payload-transform.ts +317 -0
- package/src/rich-menu.test.ts +315 -0
- package/src/rich-menu.ts +326 -0
- package/src/runtime.ts +32 -0
- package/src/send-receipt.ts +32 -0
- package/src/send.test.ts +453 -0
- package/src/send.ts +531 -0
- package/src/setup-core.ts +149 -0
- package/src/setup-runtime-api.ts +9 -0
- package/src/setup-surface.test.ts +481 -0
- package/src/setup-surface.ts +229 -0
- package/src/signature.test.ts +34 -0
- package/src/signature.ts +24 -0
- package/src/status.ts +37 -0
- package/src/template-messages.ts +333 -0
- package/src/types.ts +130 -0
- package/src/webhook-node.test.ts +598 -0
- package/src/webhook-node.ts +155 -0
- package/src/webhook-utils.ts +10 -0
- package/src/webhook.ts +135 -0
- package/tsconfig.json +16 -0
- package/api.js +0 -7
- package/channel-plugin-api.js +0 -7
- package/contract-api.js +0 -7
- package/index.js +0 -7
- package/runtime-api.js +0 -7
- package/secret-contract-api.js +0 -7
- package/setup-api.js +0 -7
- package/setup-entry.js +0 -7
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
import { i as resolveLineAccount } from "./accounts-CD4A1FE7.js";
|
|
2
|
+
import { l as validateLineMediaUrl, o as createLineSendReceipt } from "./reply-payload-transform-CDuBzoT4.js";
|
|
3
|
+
import { r as createReceiptCard } from "./schedule-cards-D-yZMHDE.js";
|
|
4
|
+
import { messagingApi } from "@line/bot-sdk";
|
|
5
|
+
import { logVerbose } from "klaw/plugin-sdk/runtime-env";
|
|
6
|
+
import { recordChannelActivity } from "klaw/plugin-sdk/channel-activity-runtime";
|
|
7
|
+
import { requireRuntimeConfig } from "klaw/plugin-sdk/plugin-config-runtime";
|
|
8
|
+
import { stripMarkdown, stripMarkdown as stripMarkdown$1 } from "klaw/plugin-sdk/text-chunking";
|
|
9
|
+
//#region extensions/line/src/flex-templates/message.ts
|
|
10
|
+
/**
|
|
11
|
+
* Wrap a FlexContainer in a FlexMessage
|
|
12
|
+
*/
|
|
13
|
+
function toFlexMessage(altText, contents) {
|
|
14
|
+
return {
|
|
15
|
+
type: "flex",
|
|
16
|
+
altText,
|
|
17
|
+
contents
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region extensions/line/src/actions.ts
|
|
22
|
+
/**
|
|
23
|
+
* Create a message action (sends text when tapped)
|
|
24
|
+
*/
|
|
25
|
+
function messageAction(label, text) {
|
|
26
|
+
return {
|
|
27
|
+
type: "message",
|
|
28
|
+
label: label.slice(0, 20),
|
|
29
|
+
text: text ?? label
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a URI action (opens a URL when tapped)
|
|
34
|
+
*/
|
|
35
|
+
function uriAction(label, uri) {
|
|
36
|
+
return {
|
|
37
|
+
type: "uri",
|
|
38
|
+
label: label.slice(0, 20),
|
|
39
|
+
uri
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create a postback action (sends data to webhook when tapped)
|
|
44
|
+
*/
|
|
45
|
+
function postbackAction(label, data, displayText) {
|
|
46
|
+
return {
|
|
47
|
+
type: "postback",
|
|
48
|
+
label: label.slice(0, 20),
|
|
49
|
+
data: data.slice(0, 300),
|
|
50
|
+
displayText: displayText?.slice(0, 300)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create a datetime picker action
|
|
55
|
+
*/
|
|
56
|
+
function datetimePickerAction(label, data, mode, options) {
|
|
57
|
+
return {
|
|
58
|
+
type: "datetimepicker",
|
|
59
|
+
label: label.slice(0, 20),
|
|
60
|
+
data: data.slice(0, 300),
|
|
61
|
+
mode,
|
|
62
|
+
initial: options?.initial,
|
|
63
|
+
max: options?.max,
|
|
64
|
+
min: options?.min
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region extensions/line/src/template-messages.ts
|
|
69
|
+
function buildTemplatePayloadAction(action) {
|
|
70
|
+
if (action.type === "uri" && action.uri) return uriAction(action.label, action.uri);
|
|
71
|
+
if (action.type === "postback" && action.data) return postbackAction(action.label, action.data, action.label);
|
|
72
|
+
return messageAction(action.label, action.data ?? action.label);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create a confirm template (yes/no style dialog)
|
|
76
|
+
*/
|
|
77
|
+
function createConfirmTemplate(text, confirmAction, cancelAction, altText) {
|
|
78
|
+
const template = {
|
|
79
|
+
type: "confirm",
|
|
80
|
+
text: text.slice(0, 240),
|
|
81
|
+
actions: [confirmAction, cancelAction]
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
type: "template",
|
|
85
|
+
altText: altText?.slice(0, 400) ?? text.slice(0, 400),
|
|
86
|
+
template
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Create a button template with title, text, and action buttons
|
|
91
|
+
*/
|
|
92
|
+
function createButtonTemplate(title, text, actions, options) {
|
|
93
|
+
const textLimit = Boolean(options?.thumbnailImageUrl?.trim()) ? 160 : 60;
|
|
94
|
+
const template = {
|
|
95
|
+
type: "buttons",
|
|
96
|
+
title: title.slice(0, 40),
|
|
97
|
+
text: text.slice(0, textLimit),
|
|
98
|
+
actions: actions.slice(0, 4),
|
|
99
|
+
thumbnailImageUrl: options?.thumbnailImageUrl,
|
|
100
|
+
imageAspectRatio: options?.imageAspectRatio ?? "rectangle",
|
|
101
|
+
imageSize: options?.imageSize ?? "cover",
|
|
102
|
+
imageBackgroundColor: options?.imageBackgroundColor,
|
|
103
|
+
defaultAction: options?.defaultAction
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
type: "template",
|
|
107
|
+
altText: options?.altText?.slice(0, 400) ?? `${title}: ${text}`.slice(0, 400),
|
|
108
|
+
template
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Create a carousel template with multiple columns
|
|
113
|
+
*/
|
|
114
|
+
function createTemplateCarousel(columns, options) {
|
|
115
|
+
const template = {
|
|
116
|
+
type: "carousel",
|
|
117
|
+
columns: columns.slice(0, 10),
|
|
118
|
+
imageAspectRatio: options?.imageAspectRatio ?? "rectangle",
|
|
119
|
+
imageSize: options?.imageSize ?? "cover"
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
type: "template",
|
|
123
|
+
altText: options?.altText?.slice(0, 400) ?? "View carousel",
|
|
124
|
+
template
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Create a carousel column for use with createTemplateCarousel
|
|
129
|
+
*/
|
|
130
|
+
function createCarouselColumn(params) {
|
|
131
|
+
return {
|
|
132
|
+
title: params.title?.slice(0, 40),
|
|
133
|
+
text: params.text.slice(0, 120),
|
|
134
|
+
actions: params.actions.slice(0, 3),
|
|
135
|
+
thumbnailImageUrl: params.thumbnailImageUrl,
|
|
136
|
+
imageBackgroundColor: params.imageBackgroundColor,
|
|
137
|
+
defaultAction: params.defaultAction
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Create an image carousel template (simpler, image-focused carousel)
|
|
142
|
+
*/
|
|
143
|
+
function createImageCarousel(columns, altText) {
|
|
144
|
+
const template = {
|
|
145
|
+
type: "image_carousel",
|
|
146
|
+
columns: columns.slice(0, 10)
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
type: "template",
|
|
150
|
+
altText: altText?.slice(0, 400) ?? "View images",
|
|
151
|
+
template
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Create an image carousel column for use with createImageCarousel
|
|
156
|
+
*/
|
|
157
|
+
function createImageCarouselColumn(imageUrl, action) {
|
|
158
|
+
return {
|
|
159
|
+
imageUrl,
|
|
160
|
+
action
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Create a simple yes/no confirmation dialog
|
|
165
|
+
*/
|
|
166
|
+
function createYesNoConfirm(question, options) {
|
|
167
|
+
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);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Create a button menu with simple text buttons
|
|
171
|
+
*/
|
|
172
|
+
function createButtonMenu(title, text, buttons, options) {
|
|
173
|
+
return createButtonTemplate(title, text, buttons.slice(0, 4).map((btn) => messageAction(btn.label, btn.text)), {
|
|
174
|
+
thumbnailImageUrl: options?.thumbnailImageUrl,
|
|
175
|
+
altText: options?.altText
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Create a button menu with URL links
|
|
180
|
+
*/
|
|
181
|
+
function createLinkMenu(title, text, links, options) {
|
|
182
|
+
return createButtonTemplate(title, text, links.slice(0, 4).map((link) => uriAction(link.label, link.url)), {
|
|
183
|
+
thumbnailImageUrl: options?.thumbnailImageUrl,
|
|
184
|
+
altText: options?.altText
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Create a simple product/item carousel
|
|
189
|
+
*/
|
|
190
|
+
function createProductCarousel(products, altText) {
|
|
191
|
+
return createTemplateCarousel(products.slice(0, 10).map((product) => {
|
|
192
|
+
const actions = [];
|
|
193
|
+
if (product.actionUrl) actions.push(uriAction(product.actionLabel ?? "View", product.actionUrl));
|
|
194
|
+
else if (product.actionData) actions.push(postbackAction(product.actionLabel ?? "Select", product.actionData));
|
|
195
|
+
else actions.push(messageAction(product.actionLabel ?? "Select", product.title));
|
|
196
|
+
return createCarouselColumn({
|
|
197
|
+
title: product.title,
|
|
198
|
+
text: product.price ? `${product.description}\n${product.price}`.slice(0, 120) : product.description,
|
|
199
|
+
thumbnailImageUrl: product.imageUrl,
|
|
200
|
+
actions
|
|
201
|
+
});
|
|
202
|
+
}), { altText });
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Convert a TemplateMessagePayload from ReplyPayload to a LINE TemplateMessage
|
|
206
|
+
*/
|
|
207
|
+
function buildTemplateMessageFromPayload(payload) {
|
|
208
|
+
switch (payload.type) {
|
|
209
|
+
case "confirm": {
|
|
210
|
+
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);
|
|
211
|
+
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);
|
|
212
|
+
return createConfirmTemplate(payload.text, confirmAction, cancelAction, payload.altText);
|
|
213
|
+
}
|
|
214
|
+
case "buttons": {
|
|
215
|
+
const actions = payload.actions.slice(0, 4).map((action) => buildTemplatePayloadAction(action));
|
|
216
|
+
return createButtonTemplate(payload.title, payload.text, actions, {
|
|
217
|
+
thumbnailImageUrl: payload.thumbnailImageUrl,
|
|
218
|
+
altText: payload.altText
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
case "carousel": return createTemplateCarousel(payload.columns.slice(0, 10).map((col) => {
|
|
222
|
+
const colActions = col.actions.slice(0, 3).map((action) => buildTemplatePayloadAction(action));
|
|
223
|
+
return createCarouselColumn({
|
|
224
|
+
title: col.title,
|
|
225
|
+
text: col.text,
|
|
226
|
+
thumbnailImageUrl: col.thumbnailImageUrl,
|
|
227
|
+
actions: colActions
|
|
228
|
+
});
|
|
229
|
+
}), { altText: payload.altText });
|
|
230
|
+
default: return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region extensions/line/src/channel-access-token.ts
|
|
235
|
+
function resolveLineChannelAccessToken(explicit, params) {
|
|
236
|
+
if (explicit?.trim()) return explicit.trim();
|
|
237
|
+
if (!params.channelAccessToken) throw new Error(`LINE channel access token missing for account "${params.accountId}" (set channels.line.channelAccessToken or LINE_CHANNEL_ACCESS_TOKEN).`);
|
|
238
|
+
return params.channelAccessToken.trim();
|
|
239
|
+
}
|
|
240
|
+
//#endregion
|
|
241
|
+
//#region extensions/line/src/send.ts
|
|
242
|
+
const userProfileCache = /* @__PURE__ */ new Map();
|
|
243
|
+
const PROFILE_CACHE_TTL_MS = 300 * 1e3;
|
|
244
|
+
function normalizeTarget(to) {
|
|
245
|
+
const trimmed = to.trim();
|
|
246
|
+
if (!trimmed) throw new Error("Recipient is required for LINE sends");
|
|
247
|
+
const normalized = trimmed.replace(/^line:group:/i, "").replace(/^line:room:/i, "").replace(/^line:user:/i, "").replace(/^line:/i, "");
|
|
248
|
+
if (!normalized) throw new Error("Recipient is required for LINE sends");
|
|
249
|
+
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): ${normalized.slice(0, 4)}…`);
|
|
250
|
+
return normalized;
|
|
251
|
+
}
|
|
252
|
+
function isLineUserChatId(chatId) {
|
|
253
|
+
return /^U/i.test(chatId);
|
|
254
|
+
}
|
|
255
|
+
function createLineMessagingClient(opts) {
|
|
256
|
+
const account = resolveLineAccount({
|
|
257
|
+
cfg: requireRuntimeConfig(opts.cfg, "LINE send"),
|
|
258
|
+
accountId: opts.accountId
|
|
259
|
+
});
|
|
260
|
+
const token = resolveLineChannelAccessToken(opts.channelAccessToken, account);
|
|
261
|
+
return {
|
|
262
|
+
account,
|
|
263
|
+
client: new messagingApi.MessagingApiClient({ channelAccessToken: token })
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function createLinePushContext(to, opts) {
|
|
267
|
+
const { account, client } = createLineMessagingClient(opts);
|
|
268
|
+
return {
|
|
269
|
+
account,
|
|
270
|
+
client,
|
|
271
|
+
chatId: normalizeTarget(to)
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function createTextMessage(text) {
|
|
275
|
+
return {
|
|
276
|
+
type: "text",
|
|
277
|
+
text
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function createImageMessage(originalContentUrl, previewImageUrl) {
|
|
281
|
+
return {
|
|
282
|
+
type: "image",
|
|
283
|
+
originalContentUrl,
|
|
284
|
+
previewImageUrl: previewImageUrl ?? originalContentUrl
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function createVideoMessage(originalContentUrl, previewImageUrl, trackingId) {
|
|
288
|
+
return {
|
|
289
|
+
type: "video",
|
|
290
|
+
originalContentUrl,
|
|
291
|
+
previewImageUrl,
|
|
292
|
+
...trackingId ? { trackingId } : {}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function createAudioMessage(originalContentUrl, durationMs) {
|
|
296
|
+
return {
|
|
297
|
+
type: "audio",
|
|
298
|
+
originalContentUrl,
|
|
299
|
+
duration: durationMs
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function createLocationMessage(location) {
|
|
303
|
+
return {
|
|
304
|
+
type: "location",
|
|
305
|
+
title: location.title.slice(0, 100),
|
|
306
|
+
address: location.address.slice(0, 100),
|
|
307
|
+
latitude: location.latitude,
|
|
308
|
+
longitude: location.longitude
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function logLineHttpError(err, context) {
|
|
312
|
+
if (!err || typeof err !== "object") return;
|
|
313
|
+
const { status, statusText, body } = err;
|
|
314
|
+
if (typeof body === "string") logVerbose(`line: ${context} failed (${status ? `${status} ${statusText ?? ""}`.trim() : "unknown status"}): ${body}`);
|
|
315
|
+
}
|
|
316
|
+
function recordLineOutboundActivity(accountId) {
|
|
317
|
+
recordChannelActivity({
|
|
318
|
+
channel: "line",
|
|
319
|
+
accountId,
|
|
320
|
+
direction: "outbound"
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
function resolveLineReceiptKind(messages) {
|
|
324
|
+
const types = new Set(messages.map((message) => message.type));
|
|
325
|
+
if (types.has("audio")) return "voice";
|
|
326
|
+
if (types.has("image") || types.has("video")) return "media";
|
|
327
|
+
if (types.has("flex") || types.has("template") || types.has("location")) return "card";
|
|
328
|
+
if (types.has("text")) return "text";
|
|
329
|
+
return "unknown";
|
|
330
|
+
}
|
|
331
|
+
async function pushLineMessages(to, messages, opts, behavior = {}) {
|
|
332
|
+
if (messages.length === 0) throw new Error("Message must be non-empty for LINE sends");
|
|
333
|
+
const { account, client, chatId } = createLinePushContext(to, opts);
|
|
334
|
+
const pushRequest = client.pushMessage({
|
|
335
|
+
to: chatId,
|
|
336
|
+
messages
|
|
337
|
+
});
|
|
338
|
+
if (behavior.errorContext) await pushRequest.catch((err) => {
|
|
339
|
+
logLineHttpError(err, behavior.errorContext);
|
|
340
|
+
throw err;
|
|
341
|
+
});
|
|
342
|
+
else await pushRequest;
|
|
343
|
+
recordLineOutboundActivity(account.accountId);
|
|
344
|
+
if (opts.verbose) logVerbose(behavior.verboseMessage?.(chatId, messages.length) ?? `line: pushed ${messages.length} messages to ${chatId}`);
|
|
345
|
+
return {
|
|
346
|
+
messageId: "push",
|
|
347
|
+
chatId,
|
|
348
|
+
receipt: createLineSendReceipt({
|
|
349
|
+
messageId: "push",
|
|
350
|
+
chatId,
|
|
351
|
+
kind: resolveLineReceiptKind(messages),
|
|
352
|
+
messageCount: messages.length
|
|
353
|
+
})
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
async function replyLineMessages(replyToken, messages, opts, behavior = {}) {
|
|
357
|
+
const { account, client } = createLineMessagingClient(opts);
|
|
358
|
+
await client.replyMessage({
|
|
359
|
+
replyToken,
|
|
360
|
+
messages
|
|
361
|
+
});
|
|
362
|
+
recordLineOutboundActivity(account.accountId);
|
|
363
|
+
if (opts.verbose) logVerbose(behavior.verboseMessage?.(messages.length) ?? `line: replied with ${messages.length} messages`);
|
|
364
|
+
}
|
|
365
|
+
async function sendMessageLine(to, text, opts) {
|
|
366
|
+
const chatId = normalizeTarget(to);
|
|
367
|
+
const messages = [];
|
|
368
|
+
const mediaUrl = opts.mediaUrl?.trim();
|
|
369
|
+
if (mediaUrl) {
|
|
370
|
+
await validateLineMediaUrl(mediaUrl);
|
|
371
|
+
switch (opts.mediaKind) {
|
|
372
|
+
case "video": {
|
|
373
|
+
const previewImageUrl = opts.previewImageUrl?.trim();
|
|
374
|
+
if (!previewImageUrl) throw new Error("LINE video messages require previewImageUrl to reference an image URL");
|
|
375
|
+
await validateLineMediaUrl(previewImageUrl);
|
|
376
|
+
const trackingId = isLineUserChatId(chatId) ? opts.trackingId : void 0;
|
|
377
|
+
messages.push(createVideoMessage(mediaUrl, previewImageUrl, trackingId));
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
case "audio":
|
|
381
|
+
messages.push(createAudioMessage(mediaUrl, opts.durationMs ?? 6e4));
|
|
382
|
+
break;
|
|
383
|
+
default:
|
|
384
|
+
{
|
|
385
|
+
const previewImageUrl = opts.previewImageUrl?.trim() || mediaUrl;
|
|
386
|
+
await validateLineMediaUrl(previewImageUrl);
|
|
387
|
+
messages.push(createImageMessage(mediaUrl, previewImageUrl));
|
|
388
|
+
}
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (text?.trim()) messages.push(createTextMessage(text.trim()));
|
|
393
|
+
if (messages.length === 0) throw new Error("Message must be non-empty for LINE sends");
|
|
394
|
+
if (opts.replyToken) {
|
|
395
|
+
await replyLineMessages(opts.replyToken, messages, opts, { verboseMessage: () => `line: replied to ${chatId}` });
|
|
396
|
+
return {
|
|
397
|
+
messageId: "reply",
|
|
398
|
+
chatId,
|
|
399
|
+
receipt: createLineSendReceipt({
|
|
400
|
+
messageId: "reply",
|
|
401
|
+
chatId,
|
|
402
|
+
kind: resolveLineReceiptKind(messages),
|
|
403
|
+
messageCount: messages.length
|
|
404
|
+
})
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
return pushLineMessages(chatId, messages, opts, { verboseMessage: (resolvedChatId) => `line: pushed message to ${resolvedChatId}` });
|
|
408
|
+
}
|
|
409
|
+
async function pushMessageLine(to, text, opts) {
|
|
410
|
+
return sendMessageLine(to, text, {
|
|
411
|
+
...opts,
|
|
412
|
+
replyToken: void 0
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
async function replyMessageLine(replyToken, messages, opts) {
|
|
416
|
+
await replyLineMessages(replyToken, messages, opts);
|
|
417
|
+
}
|
|
418
|
+
async function pushMessagesLine(to, messages, opts) {
|
|
419
|
+
return pushLineMessages(to, messages, opts, { errorContext: "push message" });
|
|
420
|
+
}
|
|
421
|
+
function createFlexMessage(altText, contents) {
|
|
422
|
+
return {
|
|
423
|
+
type: "flex",
|
|
424
|
+
altText,
|
|
425
|
+
contents
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
async function pushImageMessage(to, originalContentUrl, previewImageUrl, opts) {
|
|
429
|
+
await validateLineMediaUrl(originalContentUrl);
|
|
430
|
+
if (previewImageUrl) await validateLineMediaUrl(previewImageUrl);
|
|
431
|
+
return pushLineMessages(to, [createImageMessage(originalContentUrl, previewImageUrl)], opts, { verboseMessage: (chatId) => `line: pushed image to ${chatId}` });
|
|
432
|
+
}
|
|
433
|
+
async function pushLocationMessage(to, location, opts) {
|
|
434
|
+
return pushLineMessages(to, [createLocationMessage(location)], opts, { verboseMessage: (chatId) => `line: pushed location to ${chatId}` });
|
|
435
|
+
}
|
|
436
|
+
async function pushFlexMessage(to, altText, contents, opts) {
|
|
437
|
+
return pushLineMessages(to, [{
|
|
438
|
+
type: "flex",
|
|
439
|
+
altText: altText.slice(0, 400),
|
|
440
|
+
contents
|
|
441
|
+
}], opts, {
|
|
442
|
+
errorContext: "push flex message",
|
|
443
|
+
verboseMessage: (chatId) => `line: pushed flex message to ${chatId}`
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
async function pushTemplateMessage(to, template, opts) {
|
|
447
|
+
return pushLineMessages(to, [template], opts, { verboseMessage: (chatId) => `line: pushed template message to ${chatId}` });
|
|
448
|
+
}
|
|
449
|
+
async function pushTextMessageWithQuickReplies(to, text, quickReplyLabels, opts) {
|
|
450
|
+
return pushLineMessages(to, [createTextMessageWithQuickReplies(text, quickReplyLabels)], opts, { verboseMessage: (chatId) => `line: pushed message with quick replies to ${chatId}` });
|
|
451
|
+
}
|
|
452
|
+
function createQuickReplyItems(labels) {
|
|
453
|
+
return { items: labels.slice(0, 13).map((label) => ({
|
|
454
|
+
type: "action",
|
|
455
|
+
action: {
|
|
456
|
+
type: "message",
|
|
457
|
+
label: label.slice(0, 20),
|
|
458
|
+
text: label
|
|
459
|
+
}
|
|
460
|
+
})) };
|
|
461
|
+
}
|
|
462
|
+
function createTextMessageWithQuickReplies(text, quickReplyLabels) {
|
|
463
|
+
return {
|
|
464
|
+
type: "text",
|
|
465
|
+
text,
|
|
466
|
+
quickReply: createQuickReplyItems(quickReplyLabels)
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
async function showLoadingAnimation(chatId, opts) {
|
|
470
|
+
const { client } = createLineMessagingClient(opts);
|
|
471
|
+
try {
|
|
472
|
+
await client.showLoadingAnimation({
|
|
473
|
+
chatId: normalizeTarget(chatId),
|
|
474
|
+
loadingSeconds: opts.loadingSeconds ?? 20
|
|
475
|
+
});
|
|
476
|
+
logVerbose(`line: showing loading animation to ${chatId}`);
|
|
477
|
+
} catch (err) {
|
|
478
|
+
logVerbose(`line: loading animation failed (non-fatal): ${String(err)}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
async function getUserProfile(userId, opts) {
|
|
482
|
+
if (opts.useCache ?? true) {
|
|
483
|
+
const cached = userProfileCache.get(userId);
|
|
484
|
+
if (cached && Date.now() - cached.fetchedAt < PROFILE_CACHE_TTL_MS) return {
|
|
485
|
+
displayName: cached.displayName,
|
|
486
|
+
pictureUrl: cached.pictureUrl
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
const { client } = createLineMessagingClient(opts);
|
|
490
|
+
try {
|
|
491
|
+
const profile = await client.getProfile(userId);
|
|
492
|
+
const result = {
|
|
493
|
+
displayName: profile.displayName,
|
|
494
|
+
pictureUrl: profile.pictureUrl
|
|
495
|
+
};
|
|
496
|
+
userProfileCache.set(userId, {
|
|
497
|
+
...result,
|
|
498
|
+
fetchedAt: Date.now()
|
|
499
|
+
});
|
|
500
|
+
return result;
|
|
501
|
+
} catch (err) {
|
|
502
|
+
logVerbose(`line: failed to fetch profile for ${userId}: ${String(err)}`);
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
async function getUserDisplayName(userId, opts) {
|
|
507
|
+
return (await getUserProfile(userId, opts))?.displayName ?? userId;
|
|
508
|
+
}
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region extensions/line/src/markdown-to-line.ts
|
|
511
|
+
/**
|
|
512
|
+
* Regex patterns for markdown detection
|
|
513
|
+
*/
|
|
514
|
+
const MARKDOWN_TABLE_REGEX = /^\|(.+)\|[\r\n]+\|[-:\s|]+\|[\r\n]+((?:\|.+\|[\r\n]*)+)/gm;
|
|
515
|
+
const MARKDOWN_CODE_BLOCK_REGEX = /```(\w*)\n([\s\S]*?)```/g;
|
|
516
|
+
const MARKDOWN_LINK_REGEX = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
517
|
+
/**
|
|
518
|
+
* Detect and extract markdown tables from text
|
|
519
|
+
*/
|
|
520
|
+
function extractMarkdownTables(text) {
|
|
521
|
+
const tables = [];
|
|
522
|
+
let textWithoutTables = text;
|
|
523
|
+
MARKDOWN_TABLE_REGEX.lastIndex = 0;
|
|
524
|
+
let match;
|
|
525
|
+
const matches = [];
|
|
526
|
+
while ((match = MARKDOWN_TABLE_REGEX.exec(text)) !== null) {
|
|
527
|
+
const fullMatch = match[0];
|
|
528
|
+
const headerLine = match[1];
|
|
529
|
+
const bodyLines = match[2];
|
|
530
|
+
const headers = parseTableRow(headerLine);
|
|
531
|
+
const rows = bodyLines.trim().split(/[\r\n]+/).filter((line) => line.trim()).map(parseTableRow);
|
|
532
|
+
if (headers.length > 0 && rows.length > 0) matches.push({
|
|
533
|
+
fullMatch,
|
|
534
|
+
table: {
|
|
535
|
+
headers,
|
|
536
|
+
rows
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
541
|
+
const { fullMatch, table } = matches[i];
|
|
542
|
+
tables.unshift(table);
|
|
543
|
+
textWithoutTables = textWithoutTables.replace(fullMatch, "");
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
tables,
|
|
547
|
+
textWithoutTables
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Parse a single table row (pipe-separated values)
|
|
552
|
+
*/
|
|
553
|
+
function parseTableRow(row) {
|
|
554
|
+
return row.split("|").map((cell) => cell.trim()).filter((cell, index, arr) => {
|
|
555
|
+
if (index === 0 && cell === "") return false;
|
|
556
|
+
if (index === arr.length - 1 && cell === "") return false;
|
|
557
|
+
return true;
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Convert a markdown table to a LINE Flex Message bubble
|
|
562
|
+
*/
|
|
563
|
+
function convertTableToFlexBubble(table) {
|
|
564
|
+
const parseCell = (value) => {
|
|
565
|
+
const raw = value?.trim() ?? "";
|
|
566
|
+
if (!raw) return {
|
|
567
|
+
text: "-",
|
|
568
|
+
bold: false,
|
|
569
|
+
hasMarkup: false
|
|
570
|
+
};
|
|
571
|
+
let hasMarkup = false;
|
|
572
|
+
return {
|
|
573
|
+
text: raw.replace(/\*\*(.+?)\*\*/g, (_, inner) => {
|
|
574
|
+
hasMarkup = true;
|
|
575
|
+
return String(inner);
|
|
576
|
+
}).trim() || "-",
|
|
577
|
+
bold: /^\*\*.+\*\*$/.test(raw),
|
|
578
|
+
hasMarkup
|
|
579
|
+
};
|
|
580
|
+
};
|
|
581
|
+
const headerCells = table.headers.map((header) => parseCell(header));
|
|
582
|
+
const rowCells = table.rows.map((row) => row.map((cell) => parseCell(cell)));
|
|
583
|
+
const hasInlineMarkup = headerCells.some((cell) => cell.hasMarkup) || rowCells.some((row) => row.some((cell) => cell.hasMarkup));
|
|
584
|
+
if (table.headers.length === 2 && !hasInlineMarkup) {
|
|
585
|
+
const items = rowCells.map((row) => ({
|
|
586
|
+
name: row[0]?.text ?? "-",
|
|
587
|
+
value: row[1]?.text ?? "-"
|
|
588
|
+
}));
|
|
589
|
+
return createReceiptCard({
|
|
590
|
+
title: headerCells.map((cell) => cell.text).join(" / "),
|
|
591
|
+
items
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
return {
|
|
595
|
+
type: "bubble",
|
|
596
|
+
body: {
|
|
597
|
+
type: "box",
|
|
598
|
+
layout: "vertical",
|
|
599
|
+
contents: [
|
|
600
|
+
{
|
|
601
|
+
type: "box",
|
|
602
|
+
layout: "horizontal",
|
|
603
|
+
contents: headerCells.map((cell) => ({
|
|
604
|
+
type: "text",
|
|
605
|
+
text: cell.text,
|
|
606
|
+
weight: "bold",
|
|
607
|
+
size: "sm",
|
|
608
|
+
color: "#333333",
|
|
609
|
+
flex: 1,
|
|
610
|
+
wrap: true
|
|
611
|
+
})),
|
|
612
|
+
paddingBottom: "sm"
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
type: "separator",
|
|
616
|
+
margin: "sm"
|
|
617
|
+
},
|
|
618
|
+
...rowCells.slice(0, 10).map((row, rowIndex) => {
|
|
619
|
+
return {
|
|
620
|
+
type: "box",
|
|
621
|
+
layout: "horizontal",
|
|
622
|
+
contents: table.headers.map((_, colIndex) => {
|
|
623
|
+
const cell = row[colIndex] ?? {
|
|
624
|
+
text: "-",
|
|
625
|
+
bold: false,
|
|
626
|
+
hasMarkup: false
|
|
627
|
+
};
|
|
628
|
+
return {
|
|
629
|
+
type: "text",
|
|
630
|
+
text: cell.text,
|
|
631
|
+
size: "sm",
|
|
632
|
+
color: "#666666",
|
|
633
|
+
flex: 1,
|
|
634
|
+
wrap: true,
|
|
635
|
+
weight: cell.bold ? "bold" : void 0
|
|
636
|
+
};
|
|
637
|
+
}),
|
|
638
|
+
margin: rowIndex === 0 ? "md" : "sm"
|
|
639
|
+
};
|
|
640
|
+
})
|
|
641
|
+
],
|
|
642
|
+
paddingAll: "lg"
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Detect and extract code blocks from text
|
|
648
|
+
*/
|
|
649
|
+
function extractCodeBlocks(text) {
|
|
650
|
+
const codeBlocks = [];
|
|
651
|
+
let textWithoutCode = text;
|
|
652
|
+
MARKDOWN_CODE_BLOCK_REGEX.lastIndex = 0;
|
|
653
|
+
let match;
|
|
654
|
+
const matches = [];
|
|
655
|
+
while ((match = MARKDOWN_CODE_BLOCK_REGEX.exec(text)) !== null) {
|
|
656
|
+
const fullMatch = match[0];
|
|
657
|
+
const language = match[1] || void 0;
|
|
658
|
+
const code = match[2];
|
|
659
|
+
matches.push({
|
|
660
|
+
fullMatch,
|
|
661
|
+
block: {
|
|
662
|
+
language,
|
|
663
|
+
code: code.trim()
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
668
|
+
const { fullMatch, block } = matches[i];
|
|
669
|
+
codeBlocks.unshift(block);
|
|
670
|
+
textWithoutCode = textWithoutCode.replace(fullMatch, "");
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
codeBlocks,
|
|
674
|
+
textWithoutCode
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Convert a code block to a LINE Flex Message bubble
|
|
679
|
+
*/
|
|
680
|
+
function convertCodeBlockToFlexBubble(block) {
|
|
681
|
+
const titleText = block.language ? `Code (${block.language})` : "Code";
|
|
682
|
+
const displayCode = block.code.length > 2e3 ? block.code.slice(0, 2e3) + "\n..." : block.code;
|
|
683
|
+
return {
|
|
684
|
+
type: "bubble",
|
|
685
|
+
body: {
|
|
686
|
+
type: "box",
|
|
687
|
+
layout: "vertical",
|
|
688
|
+
contents: [{
|
|
689
|
+
type: "text",
|
|
690
|
+
text: titleText,
|
|
691
|
+
weight: "bold",
|
|
692
|
+
size: "sm",
|
|
693
|
+
color: "#666666"
|
|
694
|
+
}, {
|
|
695
|
+
type: "box",
|
|
696
|
+
layout: "vertical",
|
|
697
|
+
contents: [{
|
|
698
|
+
type: "text",
|
|
699
|
+
text: displayCode,
|
|
700
|
+
size: "xs",
|
|
701
|
+
color: "#333333",
|
|
702
|
+
wrap: true
|
|
703
|
+
}],
|
|
704
|
+
backgroundColor: "#F5F5F5",
|
|
705
|
+
paddingAll: "md",
|
|
706
|
+
cornerRadius: "md",
|
|
707
|
+
margin: "sm"
|
|
708
|
+
}],
|
|
709
|
+
paddingAll: "lg"
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Extract markdown links from text
|
|
715
|
+
*/
|
|
716
|
+
function extractLinks(text) {
|
|
717
|
+
const links = [];
|
|
718
|
+
MARKDOWN_LINK_REGEX.lastIndex = 0;
|
|
719
|
+
let match;
|
|
720
|
+
while ((match = MARKDOWN_LINK_REGEX.exec(text)) !== null) links.push({
|
|
721
|
+
text: match[1],
|
|
722
|
+
url: match[2]
|
|
723
|
+
});
|
|
724
|
+
return {
|
|
725
|
+
links,
|
|
726
|
+
textWithLinks: text.replace(MARKDOWN_LINK_REGEX, "$1")
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Create a Flex Message with tappable link buttons
|
|
731
|
+
*/
|
|
732
|
+
function convertLinksToFlexBubble(links) {
|
|
733
|
+
return {
|
|
734
|
+
type: "bubble",
|
|
735
|
+
body: {
|
|
736
|
+
type: "box",
|
|
737
|
+
layout: "vertical",
|
|
738
|
+
contents: [{
|
|
739
|
+
type: "text",
|
|
740
|
+
text: "Links",
|
|
741
|
+
weight: "bold",
|
|
742
|
+
size: "md",
|
|
743
|
+
color: "#333333"
|
|
744
|
+
}],
|
|
745
|
+
paddingAll: "lg",
|
|
746
|
+
paddingBottom: "sm"
|
|
747
|
+
},
|
|
748
|
+
footer: {
|
|
749
|
+
type: "box",
|
|
750
|
+
layout: "vertical",
|
|
751
|
+
contents: links.slice(0, 4).map((link, index) => ({
|
|
752
|
+
type: "button",
|
|
753
|
+
action: {
|
|
754
|
+
type: "uri",
|
|
755
|
+
label: link.text.slice(0, 20),
|
|
756
|
+
uri: link.url
|
|
757
|
+
},
|
|
758
|
+
style: index === 0 ? "primary" : "secondary",
|
|
759
|
+
margin: index > 0 ? "sm" : void 0
|
|
760
|
+
})),
|
|
761
|
+
paddingAll: "md"
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Main function: Process text for LINE output
|
|
767
|
+
* - Extracts tables → Flex Messages
|
|
768
|
+
* - Extracts code blocks → Flex Messages
|
|
769
|
+
* - Strips remaining markdown
|
|
770
|
+
* - Returns processed text + Flex Messages
|
|
771
|
+
*/
|
|
772
|
+
function processLineMessage(text) {
|
|
773
|
+
const flexMessages = [];
|
|
774
|
+
let processedText = text;
|
|
775
|
+
const { tables, textWithoutTables } = extractMarkdownTables(processedText);
|
|
776
|
+
processedText = textWithoutTables;
|
|
777
|
+
for (const table of tables) {
|
|
778
|
+
const bubble = convertTableToFlexBubble(table);
|
|
779
|
+
flexMessages.push(toFlexMessage("Table", bubble));
|
|
780
|
+
}
|
|
781
|
+
const { codeBlocks, textWithoutCode } = extractCodeBlocks(processedText);
|
|
782
|
+
processedText = textWithoutCode;
|
|
783
|
+
for (const block of codeBlocks) {
|
|
784
|
+
const bubble = convertCodeBlockToFlexBubble(block);
|
|
785
|
+
flexMessages.push(toFlexMessage("Code", bubble));
|
|
786
|
+
}
|
|
787
|
+
const { textWithLinks } = extractLinks(processedText);
|
|
788
|
+
processedText = textWithLinks;
|
|
789
|
+
processedText = stripMarkdown(processedText);
|
|
790
|
+
return {
|
|
791
|
+
text: processedText,
|
|
792
|
+
flexMessages
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Check if text contains markdown that needs conversion
|
|
797
|
+
*/
|
|
798
|
+
function hasMarkdownToConvert(text) {
|
|
799
|
+
MARKDOWN_TABLE_REGEX.lastIndex = 0;
|
|
800
|
+
if (MARKDOWN_TABLE_REGEX.test(text)) return true;
|
|
801
|
+
MARKDOWN_CODE_BLOCK_REGEX.lastIndex = 0;
|
|
802
|
+
if (MARKDOWN_CODE_BLOCK_REGEX.test(text)) return true;
|
|
803
|
+
if (/\*\*[^*]+\*\*/.test(text)) return true;
|
|
804
|
+
if (/~~[^~]+~~/.test(text)) return true;
|
|
805
|
+
if (/^#{1,6}\s+/m.test(text)) return true;
|
|
806
|
+
if (/^>\s+/m.test(text)) return true;
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
//#endregion
|
|
810
|
+
export { buildTemplateMessageFromPayload as A, createYesNoConfirm as B, pushMessagesLine as C, sendMessageLine as D, replyMessageLine as E, createImageCarousel as F, toFlexMessage as G, messageAction as H, createImageCarouselColumn as I, createLinkMenu as L, createButtonTemplate as M, createCarouselColumn as N, showLoadingAnimation as O, createConfirmTemplate as P, createProductCarousel as R, pushMessageLine as S, pushTextMessageWithQuickReplies as T, postbackAction as U, datetimePickerAction as V, uriAction as W, getUserDisplayName as _, extractLinks as a, pushImageMessage as b, processLineMessage as c, createFlexMessage as d, createImageMessage as f, createVideoMessage as g, createTextMessageWithQuickReplies as h, extractCodeBlocks as i, createButtonMenu as j, resolveLineChannelAccessToken as k, stripMarkdown$1 as l, createQuickReplyItems as m, convertLinksToFlexBubble as n, extractMarkdownTables as o, createLocationMessage as p, convertTableToFlexBubble as r, hasMarkdownToConvert as s, convertCodeBlockToFlexBubble as t, createAudioMessage as u, getUserProfile as v, pushTemplateMessage as w, pushLocationMessage as x, pushFlexMessage as y, createTemplateCarousel as z };
|