@kodelyth/line 2026.5.39 → 2026.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 };