@kodelyth/line 2026.5.42 → 2026.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/klaw.plugin.json +329 -2
  2. package/package.json +18 -6
  3. package/api.ts +0 -11
  4. package/channel-plugin-api.ts +0 -1
  5. package/contract-api.ts +0 -5
  6. package/index.ts +0 -53
  7. package/runtime-api.ts +0 -179
  8. package/secret-contract-api.ts +0 -4
  9. package/setup-api.ts +0 -2
  10. package/setup-entry.ts +0 -9
  11. package/src/account-helpers.ts +0 -16
  12. package/src/accounts.test.ts +0 -288
  13. package/src/accounts.ts +0 -187
  14. package/src/actions.ts +0 -61
  15. package/src/auto-reply-delivery.test.ts +0 -253
  16. package/src/auto-reply-delivery.ts +0 -200
  17. package/src/bindings.ts +0 -65
  18. package/src/bot-access.ts +0 -30
  19. package/src/bot-handlers.test.ts +0 -1094
  20. package/src/bot-handlers.ts +0 -620
  21. package/src/bot-message-context.test.ts +0 -420
  22. package/src/bot-message-context.ts +0 -586
  23. package/src/bot.ts +0 -66
  24. package/src/card-command.ts +0 -347
  25. package/src/channel-access-token.ts +0 -14
  26. package/src/channel-api.ts +0 -17
  27. package/src/channel-setup-status.contract.test.ts +0 -70
  28. package/src/channel-shared.ts +0 -48
  29. package/src/channel.logout.test.ts +0 -145
  30. package/src/channel.runtime.ts +0 -3
  31. package/src/channel.sendPayload.test.ts +0 -659
  32. package/src/channel.setup.ts +0 -11
  33. package/src/channel.status.test.ts +0 -63
  34. package/src/channel.ts +0 -155
  35. package/src/config-adapter.ts +0 -29
  36. package/src/config-schema.test.ts +0 -53
  37. package/src/config-schema.ts +0 -81
  38. package/src/download.test.ts +0 -164
  39. package/src/download.ts +0 -34
  40. package/src/flex-templates/basic-cards.ts +0 -395
  41. package/src/flex-templates/common.ts +0 -20
  42. package/src/flex-templates/media-control-cards.ts +0 -555
  43. package/src/flex-templates/message.ts +0 -13
  44. package/src/flex-templates/schedule-cards.ts +0 -467
  45. package/src/flex-templates/types.ts +0 -22
  46. package/src/flex-templates.ts +0 -32
  47. package/src/gateway.ts +0 -129
  48. package/src/group-keys.test.ts +0 -123
  49. package/src/group-keys.ts +0 -65
  50. package/src/group-policy.ts +0 -22
  51. package/src/markdown-to-line.test.ts +0 -348
  52. package/src/markdown-to-line.ts +0 -416
  53. package/src/message-cards.test.ts +0 -204
  54. package/src/monitor-durable.test.ts +0 -57
  55. package/src/monitor-durable.ts +0 -37
  56. package/src/monitor.lifecycle.test.ts +0 -499
  57. package/src/monitor.runtime.ts +0 -1
  58. package/src/monitor.ts +0 -507
  59. package/src/outbound-media.test.ts +0 -194
  60. package/src/outbound-media.ts +0 -120
  61. package/src/outbound.runtime.ts +0 -12
  62. package/src/outbound.ts +0 -427
  63. package/src/probe.contract.test.ts +0 -9
  64. package/src/probe.runtime.ts +0 -1
  65. package/src/probe.ts +0 -34
  66. package/src/quick-reply-fallback.ts +0 -10
  67. package/src/reply-chunks.test.ts +0 -180
  68. package/src/reply-chunks.ts +0 -110
  69. package/src/reply-payload-transform.test.ts +0 -392
  70. package/src/reply-payload-transform.ts +0 -317
  71. package/src/rich-menu.test.ts +0 -315
  72. package/src/rich-menu.ts +0 -326
  73. package/src/runtime.ts +0 -32
  74. package/src/send-receipt.ts +0 -32
  75. package/src/send.test.ts +0 -453
  76. package/src/send.ts +0 -531
  77. package/src/setup-core.ts +0 -149
  78. package/src/setup-runtime-api.ts +0 -9
  79. package/src/setup-surface.test.ts +0 -481
  80. package/src/setup-surface.ts +0 -229
  81. package/src/signature.test.ts +0 -34
  82. package/src/signature.ts +0 -24
  83. package/src/status.ts +0 -37
  84. package/src/template-messages.ts +0 -333
  85. package/src/types.ts +0 -130
  86. package/src/webhook-node.test.ts +0 -598
  87. package/src/webhook-node.ts +0 -155
  88. package/src/webhook-utils.ts +0 -10
  89. package/src/webhook.ts +0 -135
  90. package/tsconfig.json +0 -16
@@ -1,467 +0,0 @@
1
- import { attachFooterText } from "./common.js";
2
- import type { Action, FlexBox, FlexBubble, FlexComponent, FlexText } from "./types.js";
3
-
4
- function buildTitleSubtitleHeader(params: { title: string; subtitle?: string }): FlexComponent[] {
5
- const { title, subtitle } = params;
6
- const headerContents: FlexComponent[] = [
7
- {
8
- type: "text",
9
- text: title,
10
- weight: "bold",
11
- size: "xl",
12
- color: "#111111",
13
- wrap: true,
14
- } as FlexText,
15
- ];
16
-
17
- if (subtitle) {
18
- headerContents.push({
19
- type: "text",
20
- text: subtitle,
21
- size: "sm",
22
- color: "#888888",
23
- margin: "sm",
24
- wrap: true,
25
- } as FlexText);
26
- }
27
-
28
- return headerContents;
29
- }
30
-
31
- function buildCardHeaderSections(headerContents: FlexComponent[]): FlexComponent[] {
32
- return [
33
- {
34
- type: "box",
35
- layout: "vertical",
36
- contents: headerContents,
37
- paddingBottom: "lg",
38
- } as FlexBox,
39
- {
40
- type: "separator",
41
- color: "#EEEEEE",
42
- },
43
- ];
44
- }
45
-
46
- function createMegaBubbleWithFooter(params: {
47
- bodyContents: FlexComponent[];
48
- footer?: string;
49
- }): FlexBubble {
50
- const bubble: FlexBubble = {
51
- type: "bubble",
52
- size: "mega",
53
- body: {
54
- type: "box",
55
- layout: "vertical",
56
- contents: params.bodyContents,
57
- paddingAll: "xl",
58
- backgroundColor: "#FFFFFF",
59
- },
60
- };
61
-
62
- if (params.footer) {
63
- attachFooterText(bubble, params.footer);
64
- }
65
-
66
- return bubble;
67
- }
68
-
69
- /**
70
- * Create a receipt/summary card (for orders, transactions, data tables)
71
- *
72
- * Editorial design: Clean table layout with alternating row backgrounds,
73
- * prominent total section, and clear visual hierarchy.
74
- */
75
- export function createReceiptCard(params: {
76
- title: string;
77
- subtitle?: string;
78
- items: Array<{ name: string; value: string; highlight?: boolean }>;
79
- total?: { label: string; value: string };
80
- footer?: string;
81
- }): FlexBubble {
82
- const { title, subtitle, items, total, footer } = params;
83
-
84
- const itemRows: FlexComponent[] = items.slice(0, 12).map(
85
- (item, index) =>
86
- ({
87
- type: "box",
88
- layout: "horizontal",
89
- contents: [
90
- {
91
- type: "text",
92
- text: item.name,
93
- size: "sm",
94
- color: item.highlight ? "#111111" : "#666666",
95
- weight: item.highlight ? "bold" : "regular",
96
- flex: 3,
97
- wrap: true,
98
- } as FlexText,
99
- {
100
- type: "text",
101
- text: item.value,
102
- size: "sm",
103
- color: item.highlight ? "#06C755" : "#333333",
104
- weight: item.highlight ? "bold" : "regular",
105
- flex: 2,
106
- align: "end",
107
- wrap: true,
108
- } as FlexText,
109
- ],
110
- paddingAll: "md",
111
- backgroundColor: index % 2 === 0 ? "#FFFFFF" : "#FAFAFA",
112
- }) as FlexBox,
113
- );
114
-
115
- // Header section
116
- const headerContents = buildTitleSubtitleHeader({ title, subtitle });
117
-
118
- const bodyContents: FlexComponent[] = [
119
- ...buildCardHeaderSections(headerContents),
120
- {
121
- type: "box",
122
- layout: "vertical",
123
- contents: itemRows,
124
- margin: "md",
125
- cornerRadius: "md",
126
- borderWidth: "light",
127
- borderColor: "#EEEEEE",
128
- } as FlexBox,
129
- ];
130
-
131
- // Total section with emphasis
132
- if (total) {
133
- bodyContents.push({
134
- type: "box",
135
- layout: "horizontal",
136
- contents: [
137
- {
138
- type: "text",
139
- text: total.label,
140
- size: "lg",
141
- weight: "bold",
142
- color: "#111111",
143
- flex: 2,
144
- } as FlexText,
145
- {
146
- type: "text",
147
- text: total.value,
148
- size: "xl",
149
- weight: "bold",
150
- color: "#06C755",
151
- flex: 2,
152
- align: "end",
153
- } as FlexText,
154
- ],
155
- margin: "xl",
156
- paddingAll: "lg",
157
- backgroundColor: "#F0FDF4",
158
- cornerRadius: "lg",
159
- } as FlexBox);
160
- }
161
-
162
- return createMegaBubbleWithFooter({ bodyContents, footer });
163
- }
164
-
165
- /**
166
- * Create a calendar event card (for meetings, appointments, reminders)
167
- *
168
- * Editorial design: Date as hero, strong typographic hierarchy,
169
- * color-blocked zones, full text wrapping for readability.
170
- */
171
- export function createEventCard(params: {
172
- title: string;
173
- date: string;
174
- time?: string;
175
- location?: string;
176
- description?: string;
177
- calendar?: string;
178
- isAllDay?: boolean;
179
- action?: Action;
180
- }): FlexBubble {
181
- const { title, date, time, location, description, calendar, isAllDay, action } = params;
182
-
183
- // Hero date block - the most important information
184
- const dateBlock: FlexBox = {
185
- type: "box",
186
- layout: "vertical",
187
- contents: [
188
- {
189
- type: "text",
190
- text: date.toUpperCase(),
191
- size: "sm",
192
- weight: "bold",
193
- color: "#06C755",
194
- wrap: true,
195
- } as FlexText,
196
- {
197
- type: "text",
198
- text: isAllDay ? "ALL DAY" : (time ?? ""),
199
- size: "xxl",
200
- weight: "bold",
201
- color: "#111111",
202
- wrap: true,
203
- margin: "xs",
204
- } as FlexText,
205
- ],
206
- paddingBottom: "lg",
207
- borderWidth: "none",
208
- };
209
-
210
- // If no time and not all day, hide the time display
211
- if (!time && !isAllDay) {
212
- dateBlock.contents = [
213
- {
214
- type: "text",
215
- text: date,
216
- size: "xl",
217
- weight: "bold",
218
- color: "#111111",
219
- wrap: true,
220
- } as FlexText,
221
- ];
222
- }
223
-
224
- // Event title with accent bar
225
- const titleBlock: FlexBox = {
226
- type: "box",
227
- layout: "horizontal",
228
- contents: [
229
- {
230
- type: "box",
231
- layout: "vertical",
232
- contents: [],
233
- width: "4px",
234
- backgroundColor: "#06C755",
235
- cornerRadius: "2px",
236
- } as FlexBox,
237
- {
238
- type: "box",
239
- layout: "vertical",
240
- contents: [
241
- {
242
- type: "text",
243
- text: title,
244
- size: "lg",
245
- weight: "bold",
246
- color: "#1a1a1a",
247
- wrap: true,
248
- } as FlexText,
249
- ...(calendar
250
- ? [
251
- {
252
- type: "text",
253
- text: calendar,
254
- size: "xs",
255
- color: "#888888",
256
- margin: "sm",
257
- wrap: true,
258
- } as FlexText,
259
- ]
260
- : []),
261
- ],
262
- flex: 1,
263
- paddingStart: "lg",
264
- } as FlexBox,
265
- ],
266
- paddingTop: "lg",
267
- paddingBottom: "lg",
268
- borderWidth: "light",
269
- borderColor: "#EEEEEE",
270
- };
271
-
272
- const bodyContents: FlexComponent[] = [dateBlock, titleBlock];
273
-
274
- // Details section (location + description) in subtle background
275
- const hasDetails = location || description;
276
- if (hasDetails) {
277
- const detailItems: FlexComponent[] = [];
278
-
279
- if (location) {
280
- detailItems.push({
281
- type: "box",
282
- layout: "horizontal",
283
- contents: [
284
- {
285
- type: "text",
286
- text: "📍",
287
- size: "sm",
288
- flex: 0,
289
- } as FlexText,
290
- {
291
- type: "text",
292
- text: location,
293
- size: "sm",
294
- color: "#444444",
295
- margin: "md",
296
- flex: 1,
297
- wrap: true,
298
- } as FlexText,
299
- ],
300
- alignItems: "flex-start",
301
- } as FlexBox);
302
- }
303
-
304
- if (description) {
305
- detailItems.push({
306
- type: "text",
307
- text: description,
308
- size: "sm",
309
- color: "#666666",
310
- wrap: true,
311
- margin: location ? "lg" : "none",
312
- } as FlexText);
313
- }
314
-
315
- bodyContents.push({
316
- type: "box",
317
- layout: "vertical",
318
- contents: detailItems,
319
- margin: "lg",
320
- paddingAll: "lg",
321
- backgroundColor: "#F8F9FA",
322
- cornerRadius: "lg",
323
- } as FlexBox);
324
- }
325
-
326
- return {
327
- type: "bubble",
328
- size: "mega",
329
- body: {
330
- type: "box",
331
- layout: "vertical",
332
- contents: bodyContents,
333
- paddingAll: "xl",
334
- backgroundColor: "#FFFFFF",
335
- action,
336
- },
337
- };
338
- }
339
-
340
- /**
341
- * Create a calendar agenda card showing multiple events
342
- *
343
- * Editorial timeline design: Time-focused left column with event details
344
- * on the right. Visual accent bars indicate event priority/recency.
345
- */
346
- export function createAgendaCard(params: {
347
- title: string;
348
- subtitle?: string;
349
- events: Array<{
350
- title: string;
351
- time?: string;
352
- location?: string;
353
- calendar?: string;
354
- isNow?: boolean;
355
- }>;
356
- footer?: string;
357
- }): FlexBubble {
358
- const { title, subtitle, events, footer } = params;
359
-
360
- // Header with title and optional subtitle
361
- const headerContents = buildTitleSubtitleHeader({ title, subtitle });
362
-
363
- // Event timeline items
364
- const eventItems: FlexComponent[] = events.slice(0, 6).map((event, index) => {
365
- const isActive = event.isNow || index === 0;
366
- const accentColor = isActive ? "#06C755" : "#E5E5E5";
367
-
368
- // Time column (fixed width)
369
- const timeColumn: FlexBox = {
370
- type: "box",
371
- layout: "vertical",
372
- contents: [
373
- {
374
- type: "text",
375
- text: event.time ?? "—",
376
- size: "sm",
377
- weight: isActive ? "bold" : "regular",
378
- color: isActive ? "#06C755" : "#666666",
379
- align: "end",
380
- wrap: true,
381
- } as FlexText,
382
- ],
383
- width: "65px",
384
- justifyContent: "flex-start",
385
- };
386
-
387
- // Accent dot
388
- const dotColumn: FlexBox = {
389
- type: "box",
390
- layout: "vertical",
391
- contents: [
392
- {
393
- type: "box",
394
- layout: "vertical",
395
- contents: [],
396
- width: "10px",
397
- height: "10px",
398
- backgroundColor: accentColor,
399
- cornerRadius: "5px",
400
- } as FlexBox,
401
- ],
402
- width: "24px",
403
- alignItems: "center",
404
- justifyContent: "flex-start",
405
- paddingTop: "xs",
406
- };
407
-
408
- // Event details column
409
- const detailContents: FlexComponent[] = [
410
- {
411
- type: "text",
412
- text: event.title,
413
- size: "md",
414
- weight: "bold",
415
- color: "#1a1a1a",
416
- wrap: true,
417
- } as FlexText,
418
- ];
419
-
420
- // Secondary info line
421
- const secondaryParts: string[] = [];
422
- if (event.location) {
423
- secondaryParts.push(event.location);
424
- }
425
- if (event.calendar) {
426
- secondaryParts.push(event.calendar);
427
- }
428
-
429
- if (secondaryParts.length > 0) {
430
- detailContents.push({
431
- type: "text",
432
- text: secondaryParts.join(" · "),
433
- size: "xs",
434
- color: "#888888",
435
- wrap: true,
436
- margin: "xs",
437
- } as FlexText);
438
- }
439
-
440
- const detailColumn: FlexBox = {
441
- type: "box",
442
- layout: "vertical",
443
- contents: detailContents,
444
- flex: 1,
445
- };
446
-
447
- return {
448
- type: "box",
449
- layout: "horizontal",
450
- contents: [timeColumn, dotColumn, detailColumn],
451
- margin: index > 0 ? "xl" : undefined,
452
- alignItems: "flex-start",
453
- } as FlexBox;
454
- });
455
-
456
- const bodyContents: FlexComponent[] = [
457
- ...buildCardHeaderSections(headerContents),
458
- {
459
- type: "box",
460
- layout: "vertical",
461
- contents: eventItems,
462
- paddingTop: "xl",
463
- } as FlexBox,
464
- ];
465
-
466
- return createMegaBubbleWithFooter({ bodyContents, footer });
467
- }
@@ -1,22 +0,0 @@
1
- import type { messagingApi } from "@line/bot-sdk";
2
-
3
- export type FlexContainer = messagingApi.FlexContainer;
4
- export type FlexBubble = messagingApi.FlexBubble;
5
- export type FlexCarousel = messagingApi.FlexCarousel;
6
- export type FlexBox = messagingApi.FlexBox;
7
- export type FlexText = messagingApi.FlexText;
8
- export type FlexImage = messagingApi.FlexImage;
9
- export type FlexButton = messagingApi.FlexButton;
10
- export type FlexComponent = messagingApi.FlexComponent;
11
- export type Action = messagingApi.Action;
12
-
13
- export interface ListItem {
14
- title: string;
15
- subtitle?: string;
16
- action?: Action;
17
- }
18
-
19
- export interface CardAction {
20
- label: string;
21
- action: Action;
22
- }
@@ -1,32 +0,0 @@
1
- export {
2
- createActionCard,
3
- createCarousel,
4
- createImageCard,
5
- createInfoCard,
6
- createListCard,
7
- createNotificationBubble,
8
- } from "./flex-templates/basic-cards.js";
9
- export {
10
- createAgendaCard,
11
- createEventCard,
12
- createReceiptCard,
13
- } from "./flex-templates/schedule-cards.js";
14
- export {
15
- createAppleTvRemoteCard,
16
- createDeviceControlCard,
17
- createMediaPlayerCard,
18
- } from "./flex-templates/media-control-cards.js";
19
- export { toFlexMessage } from "./flex-templates/message.js";
20
-
21
- export type {
22
- CardAction,
23
- FlexBox,
24
- FlexBubble,
25
- FlexButton,
26
- FlexCarousel,
27
- FlexComponent,
28
- FlexContainer,
29
- FlexImage,
30
- FlexText,
31
- ListItem,
32
- } from "./flex-templates/types.js";
package/src/gateway.ts DELETED
@@ -1,129 +0,0 @@
1
- import { createLazyRuntimeModule } from "klaw/plugin-sdk/lazy-runtime";
2
- import { resolveLineAccount } from "./accounts.js";
3
- import {
4
- clearAccountEntryFields,
5
- DEFAULT_ACCOUNT_ID,
6
- type ChannelPlugin,
7
- type LineConfig,
8
- type KlawConfig,
9
- type ResolvedLineAccount,
10
- } from "./channel-api.js";
11
- import { getLineRuntime } from "./runtime.js";
12
-
13
- const loadLineProbeRuntime = createLazyRuntimeModule(() => import("./probe.runtime.js"));
14
- const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime.js"));
15
-
16
- export const lineGatewayAdapter: NonNullable<ChannelPlugin<ResolvedLineAccount>["gateway"]> = {
17
- startAccount: async (ctx) => {
18
- const account = ctx.account;
19
- const token = account.channelAccessToken.trim();
20
- const secret = account.channelSecret.trim();
21
- if (!token) {
22
- throw new Error(
23
- `LINE webhook mode requires a non-empty channel access token for account "${account.accountId}".`,
24
- );
25
- }
26
- if (!secret) {
27
- throw new Error(
28
- `LINE webhook mode requires a non-empty channel secret for account "${account.accountId}".`,
29
- );
30
- }
31
-
32
- let lineBotLabel = "";
33
- try {
34
- const probe = await (await loadLineProbeRuntime()).probeLineBot(token, 2500);
35
- const displayName = probe.ok ? probe.bot?.displayName?.trim() : null;
36
- if (displayName) {
37
- lineBotLabel = ` (${displayName})`;
38
- }
39
- } catch (err) {
40
- if (getLineRuntime().logging.shouldLogVerbose()) {
41
- ctx.log?.debug?.(`[${account.accountId}] bot probe failed: ${String(err)}`);
42
- }
43
- }
44
-
45
- ctx.log?.info(`[${account.accountId}] starting LINE provider${lineBotLabel}`);
46
-
47
- const monitorLineProvider =
48
- getLineRuntime().channel.line?.monitorLineProvider ??
49
- (await loadLineMonitorRuntime()).monitorLineProvider;
50
-
51
- return await monitorLineProvider({
52
- channelAccessToken: token,
53
- channelSecret: secret,
54
- accountId: account.accountId,
55
- config: ctx.cfg,
56
- runtime: ctx.runtime,
57
- abortSignal: ctx.abortSignal,
58
- webhookPath: account.config.webhookPath,
59
- });
60
- },
61
- logoutAccount: async ({ accountId, cfg }) => {
62
- const envToken = process.env.LINE_CHANNEL_ACCESS_TOKEN?.trim() ?? "";
63
- const nextCfg = { ...cfg } as KlawConfig;
64
- const lineConfig = (cfg.channels?.line ?? {}) as LineConfig;
65
- const nextLine = { ...lineConfig };
66
- let cleared = false;
67
- let changed = false;
68
-
69
- if (accountId === DEFAULT_ACCOUNT_ID) {
70
- if (
71
- nextLine.channelAccessToken ||
72
- nextLine.channelSecret ||
73
- nextLine.tokenFile ||
74
- nextLine.secretFile
75
- ) {
76
- delete nextLine.channelAccessToken;
77
- delete nextLine.channelSecret;
78
- delete nextLine.tokenFile;
79
- delete nextLine.secretFile;
80
- cleared = true;
81
- changed = true;
82
- }
83
- }
84
-
85
- const accountCleanup = clearAccountEntryFields({
86
- accounts: nextLine.accounts,
87
- accountId,
88
- fields: ["channelAccessToken", "channelSecret", "tokenFile", "secretFile"],
89
- markClearedOnFieldPresence: true,
90
- });
91
- if (accountCleanup.changed) {
92
- changed = true;
93
- if (accountCleanup.cleared) {
94
- cleared = true;
95
- }
96
- if (accountCleanup.nextAccounts) {
97
- nextLine.accounts = accountCleanup.nextAccounts;
98
- } else {
99
- delete nextLine.accounts;
100
- }
101
- }
102
-
103
- if (changed) {
104
- if (Object.keys(nextLine).length > 0) {
105
- nextCfg.channels = { ...nextCfg.channels, line: nextLine };
106
- } else {
107
- const nextChannels = { ...nextCfg.channels };
108
- delete (nextChannels as Record<string, unknown>).line;
109
- if (Object.keys(nextChannels).length > 0) {
110
- nextCfg.channels = nextChannels;
111
- } else {
112
- delete nextCfg.channels;
113
- }
114
- }
115
- await getLineRuntime().config.replaceConfigFile({
116
- nextConfig: nextCfg,
117
- afterWrite: { mode: "auto" },
118
- });
119
- }
120
-
121
- const resolved = resolveLineAccount({
122
- cfg: changed ? nextCfg : cfg,
123
- accountId,
124
- });
125
- const loggedOut = resolved.tokenSource === "none";
126
-
127
- return { cleared, envToken: Boolean(envToken), loggedOut };
128
- },
129
- };