@gakr-gakr/line 0.1.0

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 (66) hide show
  1. package/api.ts +11 -0
  2. package/autobot.plugin.json +15 -0
  3. package/channel-plugin-api.ts +1 -0
  4. package/contract-api.ts +5 -0
  5. package/index.ts +54 -0
  6. package/package.json +60 -0
  7. package/runtime-api.ts +182 -0
  8. package/secret-contract-api.ts +4 -0
  9. package/setup-api.ts +2 -0
  10. package/setup-entry.ts +9 -0
  11. package/src/account-helpers.ts +16 -0
  12. package/src/accounts.ts +187 -0
  13. package/src/actions.ts +61 -0
  14. package/src/auto-reply-delivery.ts +200 -0
  15. package/src/bindings.ts +65 -0
  16. package/src/bot-access.ts +30 -0
  17. package/src/bot-handlers.ts +620 -0
  18. package/src/bot-message-context.ts +586 -0
  19. package/src/bot.ts +70 -0
  20. package/src/card-command.ts +347 -0
  21. package/src/channel-access-token.ts +14 -0
  22. package/src/channel-api.ts +17 -0
  23. package/src/channel-shared.ts +48 -0
  24. package/src/channel.runtime.ts +3 -0
  25. package/src/channel.setup.ts +11 -0
  26. package/src/channel.ts +155 -0
  27. package/src/config-adapter.ts +29 -0
  28. package/src/config-schema.ts +81 -0
  29. package/src/download.ts +34 -0
  30. package/src/flex-templates/basic-cards.ts +395 -0
  31. package/src/flex-templates/common.ts +20 -0
  32. package/src/flex-templates/media-control-cards.ts +555 -0
  33. package/src/flex-templates/message.ts +13 -0
  34. package/src/flex-templates/schedule-cards.ts +467 -0
  35. package/src/flex-templates/types.ts +22 -0
  36. package/src/flex-templates.ts +32 -0
  37. package/src/gateway.ts +129 -0
  38. package/src/group-keys.ts +65 -0
  39. package/src/group-policy.ts +22 -0
  40. package/src/markdown-to-line.ts +416 -0
  41. package/src/monitor-durable.ts +37 -0
  42. package/src/monitor.runtime.ts +1 -0
  43. package/src/monitor.ts +507 -0
  44. package/src/outbound-media.ts +120 -0
  45. package/src/outbound.runtime.ts +12 -0
  46. package/src/outbound.ts +427 -0
  47. package/src/probe.runtime.ts +1 -0
  48. package/src/probe.ts +34 -0
  49. package/src/quick-reply-fallback.ts +10 -0
  50. package/src/reply-chunks.ts +110 -0
  51. package/src/reply-payload-transform.ts +317 -0
  52. package/src/rich-menu.ts +326 -0
  53. package/src/runtime.ts +32 -0
  54. package/src/send-receipt.ts +32 -0
  55. package/src/send.ts +531 -0
  56. package/src/setup-core.ts +149 -0
  57. package/src/setup-runtime-api.ts +9 -0
  58. package/src/setup-surface.ts +229 -0
  59. package/src/signature.ts +24 -0
  60. package/src/status.ts +37 -0
  61. package/src/template-messages.ts +333 -0
  62. package/src/types.ts +130 -0
  63. package/src/webhook-node.ts +155 -0
  64. package/src/webhook-utils.ts +10 -0
  65. package/src/webhook.ts +135 -0
  66. package/tsconfig.json +16 -0
@@ -0,0 +1,555 @@
1
+ import type {
2
+ FlexBox,
3
+ FlexBubble,
4
+ FlexButton,
5
+ FlexComponent,
6
+ FlexImage,
7
+ FlexText,
8
+ } from "./types.js";
9
+
10
+ /**
11
+ * Create a media player card for Sonos, Spotify, Apple Music, etc.
12
+ *
13
+ * Editorial design: Album art hero with gradient overlay for text,
14
+ * prominent now-playing indicator, refined playback controls.
15
+ */
16
+ export function createMediaPlayerCard(params: {
17
+ title: string;
18
+ subtitle?: string;
19
+ source?: string;
20
+ imageUrl?: string;
21
+ isPlaying?: boolean;
22
+ progress?: string;
23
+ controls?: {
24
+ previous?: { data: string };
25
+ play?: { data: string };
26
+ pause?: { data: string };
27
+ next?: { data: string };
28
+ };
29
+ extraActions?: Array<{ label: string; data: string }>;
30
+ }): FlexBubble {
31
+ const { title, subtitle, source, imageUrl, isPlaying, progress, controls, extraActions } = params;
32
+
33
+ // Track info section
34
+ const trackInfo: FlexComponent[] = [
35
+ {
36
+ type: "text",
37
+ text: title,
38
+ weight: "bold",
39
+ size: "xl",
40
+ color: "#111111",
41
+ wrap: true,
42
+ } as FlexText,
43
+ ];
44
+
45
+ if (subtitle) {
46
+ trackInfo.push({
47
+ type: "text",
48
+ text: subtitle,
49
+ size: "md",
50
+ color: "#666666",
51
+ wrap: true,
52
+ margin: "sm",
53
+ } as FlexText);
54
+ }
55
+
56
+ // Status row with source and playing indicator
57
+ const statusItems: FlexComponent[] = [];
58
+
59
+ if (isPlaying !== undefined) {
60
+ statusItems.push({
61
+ type: "box",
62
+ layout: "horizontal",
63
+ contents: [
64
+ {
65
+ type: "box",
66
+ layout: "vertical",
67
+ contents: [],
68
+ width: "8px",
69
+ height: "8px",
70
+ backgroundColor: isPlaying ? "#06C755" : "#CCCCCC",
71
+ cornerRadius: "4px",
72
+ } as FlexBox,
73
+ {
74
+ type: "text",
75
+ text: isPlaying ? "Now Playing" : "Paused",
76
+ size: "xs",
77
+ color: isPlaying ? "#06C755" : "#888888",
78
+ weight: "bold",
79
+ margin: "sm",
80
+ } as FlexText,
81
+ ],
82
+ alignItems: "center",
83
+ } as FlexBox);
84
+ }
85
+
86
+ if (source) {
87
+ statusItems.push({
88
+ type: "text",
89
+ text: source,
90
+ size: "xs",
91
+ color: "#AAAAAA",
92
+ margin: statusItems.length > 0 ? "lg" : undefined,
93
+ } as FlexText);
94
+ }
95
+
96
+ if (progress) {
97
+ statusItems.push({
98
+ type: "text",
99
+ text: progress,
100
+ size: "xs",
101
+ color: "#888888",
102
+ align: "end",
103
+ flex: 1,
104
+ } as FlexText);
105
+ }
106
+
107
+ const bodyContents: FlexComponent[] = [
108
+ {
109
+ type: "box",
110
+ layout: "vertical",
111
+ contents: trackInfo,
112
+ } as FlexBox,
113
+ ];
114
+
115
+ if (statusItems.length > 0) {
116
+ bodyContents.push({
117
+ type: "box",
118
+ layout: "horizontal",
119
+ contents: statusItems,
120
+ margin: "lg",
121
+ alignItems: "center",
122
+ } as FlexBox);
123
+ }
124
+
125
+ const bubble: FlexBubble = {
126
+ type: "bubble",
127
+ size: "mega",
128
+ body: {
129
+ type: "box",
130
+ layout: "vertical",
131
+ contents: bodyContents,
132
+ paddingAll: "xl",
133
+ backgroundColor: "#FFFFFF",
134
+ },
135
+ };
136
+
137
+ // Album art hero
138
+ if (imageUrl) {
139
+ bubble.hero = {
140
+ type: "image",
141
+ url: imageUrl,
142
+ size: "full",
143
+ aspectRatio: "1:1",
144
+ aspectMode: "cover",
145
+ } as FlexImage;
146
+ }
147
+
148
+ // Control buttons in footer
149
+ if (controls || extraActions?.length) {
150
+ const footerContents: FlexComponent[] = [];
151
+
152
+ // Main playback controls with refined styling
153
+ if (controls) {
154
+ const controlButtons: FlexComponent[] = [];
155
+
156
+ if (controls.previous) {
157
+ controlButtons.push({
158
+ type: "button",
159
+ action: {
160
+ type: "postback",
161
+ label: "⏮",
162
+ data: controls.previous.data,
163
+ },
164
+ style: "secondary",
165
+ flex: 1,
166
+ height: "sm",
167
+ } as FlexButton);
168
+ }
169
+
170
+ if (controls.play) {
171
+ controlButtons.push({
172
+ type: "button",
173
+ action: {
174
+ type: "postback",
175
+ label: "▶",
176
+ data: controls.play.data,
177
+ },
178
+ style: isPlaying ? "secondary" : "primary",
179
+ flex: 1,
180
+ height: "sm",
181
+ margin: controls.previous ? "md" : undefined,
182
+ } as FlexButton);
183
+ }
184
+
185
+ if (controls.pause) {
186
+ controlButtons.push({
187
+ type: "button",
188
+ action: {
189
+ type: "postback",
190
+ label: "⏸",
191
+ data: controls.pause.data,
192
+ },
193
+ style: isPlaying ? "primary" : "secondary",
194
+ flex: 1,
195
+ height: "sm",
196
+ margin: controlButtons.length > 0 ? "md" : undefined,
197
+ } as FlexButton);
198
+ }
199
+
200
+ if (controls.next) {
201
+ controlButtons.push({
202
+ type: "button",
203
+ action: {
204
+ type: "postback",
205
+ label: "⏭",
206
+ data: controls.next.data,
207
+ },
208
+ style: "secondary",
209
+ flex: 1,
210
+ height: "sm",
211
+ margin: controlButtons.length > 0 ? "md" : undefined,
212
+ } as FlexButton);
213
+ }
214
+
215
+ if (controlButtons.length > 0) {
216
+ footerContents.push({
217
+ type: "box",
218
+ layout: "horizontal",
219
+ contents: controlButtons,
220
+ } as FlexBox);
221
+ }
222
+ }
223
+
224
+ // Extra actions
225
+ if (extraActions?.length) {
226
+ footerContents.push({
227
+ type: "box",
228
+ layout: "horizontal",
229
+ contents: extraActions.slice(0, 2).map(
230
+ (action, index) =>
231
+ ({
232
+ type: "button",
233
+ action: {
234
+ type: "postback",
235
+ label: action.label.slice(0, 15),
236
+ data: action.data,
237
+ },
238
+ style: "secondary",
239
+ flex: 1,
240
+ height: "sm",
241
+ margin: index > 0 ? "md" : undefined,
242
+ }) as FlexButton,
243
+ ),
244
+ margin: "md",
245
+ } as FlexBox);
246
+ }
247
+
248
+ if (footerContents.length > 0) {
249
+ bubble.footer = {
250
+ type: "box",
251
+ layout: "vertical",
252
+ contents: footerContents,
253
+ paddingAll: "lg",
254
+ backgroundColor: "#FAFAFA",
255
+ };
256
+ }
257
+ }
258
+
259
+ return bubble;
260
+ }
261
+
262
+ /**
263
+ * Create an Apple TV remote card with a D-pad and control rows.
264
+ */
265
+ export function createAppleTvRemoteCard(params: {
266
+ deviceName: string;
267
+ status?: string;
268
+ actionData: {
269
+ up: string;
270
+ down: string;
271
+ left: string;
272
+ right: string;
273
+ select: string;
274
+ menu: string;
275
+ home: string;
276
+ play: string;
277
+ pause: string;
278
+ volumeUp: string;
279
+ volumeDown: string;
280
+ mute: string;
281
+ };
282
+ }): FlexBubble {
283
+ const { deviceName, status, actionData } = params;
284
+
285
+ const headerContents: FlexComponent[] = [
286
+ {
287
+ type: "text",
288
+ text: deviceName,
289
+ weight: "bold",
290
+ size: "xl",
291
+ color: "#111111",
292
+ wrap: true,
293
+ } as FlexText,
294
+ ];
295
+
296
+ if (status) {
297
+ headerContents.push({
298
+ type: "text",
299
+ text: status,
300
+ size: "sm",
301
+ color: "#666666",
302
+ wrap: true,
303
+ margin: "sm",
304
+ } as FlexText);
305
+ }
306
+
307
+ const makeButton = (
308
+ label: string,
309
+ data: string,
310
+ style: "primary" | "secondary" = "secondary",
311
+ ): FlexButton => ({
312
+ type: "button",
313
+ action: {
314
+ type: "postback",
315
+ label,
316
+ data,
317
+ },
318
+ style,
319
+ height: "sm",
320
+ flex: 1,
321
+ });
322
+
323
+ const dpadRows: FlexComponent[] = [
324
+ {
325
+ type: "box",
326
+ layout: "horizontal",
327
+ contents: [{ type: "filler" }, makeButton("↑", actionData.up), { type: "filler" }],
328
+ } as FlexBox,
329
+ {
330
+ type: "box",
331
+ layout: "horizontal",
332
+ contents: [
333
+ makeButton("←", actionData.left),
334
+ makeButton("OK", actionData.select, "primary"),
335
+ makeButton("→", actionData.right),
336
+ ],
337
+ margin: "md",
338
+ } as FlexBox,
339
+ {
340
+ type: "box",
341
+ layout: "horizontal",
342
+ contents: [{ type: "filler" }, makeButton("↓", actionData.down), { type: "filler" }],
343
+ margin: "md",
344
+ } as FlexBox,
345
+ ];
346
+
347
+ const menuRow: FlexComponent = {
348
+ type: "box",
349
+ layout: "horizontal",
350
+ contents: [makeButton("Menu", actionData.menu), makeButton("Home", actionData.home)],
351
+ margin: "lg",
352
+ } as FlexBox;
353
+
354
+ const playbackRow: FlexComponent = {
355
+ type: "box",
356
+ layout: "horizontal",
357
+ contents: [makeButton("Play", actionData.play), makeButton("Pause", actionData.pause)],
358
+ margin: "md",
359
+ } as FlexBox;
360
+
361
+ const volumeRow: FlexComponent = {
362
+ type: "box",
363
+ layout: "horizontal",
364
+ contents: [
365
+ makeButton("Vol +", actionData.volumeUp),
366
+ makeButton("Mute", actionData.mute),
367
+ makeButton("Vol -", actionData.volumeDown),
368
+ ],
369
+ margin: "md",
370
+ } as FlexBox;
371
+
372
+ return {
373
+ type: "bubble",
374
+ size: "mega",
375
+ body: {
376
+ type: "box",
377
+ layout: "vertical",
378
+ contents: [
379
+ {
380
+ type: "box",
381
+ layout: "vertical",
382
+ contents: headerContents,
383
+ } as FlexBox,
384
+ {
385
+ type: "separator",
386
+ margin: "lg",
387
+ color: "#EEEEEE",
388
+ },
389
+ ...dpadRows,
390
+ menuRow,
391
+ playbackRow,
392
+ volumeRow,
393
+ ],
394
+ paddingAll: "xl",
395
+ backgroundColor: "#FFFFFF",
396
+ },
397
+ };
398
+ }
399
+
400
+ /**
401
+ * Create a device control card for Apple TV, smart home devices, etc.
402
+ *
403
+ * Editorial design: Device-focused header with status indicator,
404
+ * clean control grid with clear visual hierarchy.
405
+ */
406
+ export function createDeviceControlCard(params: {
407
+ deviceName: string;
408
+ deviceType?: string;
409
+ status?: string;
410
+ isOnline?: boolean;
411
+ imageUrl?: string;
412
+ controls: Array<{
413
+ label: string;
414
+ icon?: string;
415
+ data: string;
416
+ style?: "primary" | "secondary";
417
+ }>;
418
+ }): FlexBubble {
419
+ const { deviceName, deviceType, status, isOnline, imageUrl, controls } = params;
420
+
421
+ // Device header with status indicator
422
+ const headerContents: FlexComponent[] = [
423
+ {
424
+ type: "box",
425
+ layout: "horizontal",
426
+ contents: [
427
+ // Status dot
428
+ {
429
+ type: "box",
430
+ layout: "vertical",
431
+ contents: [],
432
+ width: "10px",
433
+ height: "10px",
434
+ backgroundColor: isOnline !== false ? "#06C755" : "#FF5555",
435
+ cornerRadius: "5px",
436
+ } as FlexBox,
437
+ {
438
+ type: "text",
439
+ text: deviceName,
440
+ weight: "bold",
441
+ size: "xl",
442
+ color: "#111111",
443
+ wrap: true,
444
+ flex: 1,
445
+ margin: "md",
446
+ } as FlexText,
447
+ ],
448
+ alignItems: "center",
449
+ } as FlexBox,
450
+ ];
451
+
452
+ if (deviceType) {
453
+ headerContents.push({
454
+ type: "text",
455
+ text: deviceType,
456
+ size: "sm",
457
+ color: "#888888",
458
+ margin: "sm",
459
+ } as FlexText);
460
+ }
461
+
462
+ if (status) {
463
+ headerContents.push({
464
+ type: "box",
465
+ layout: "vertical",
466
+ contents: [
467
+ {
468
+ type: "text",
469
+ text: status,
470
+ size: "sm",
471
+ color: "#444444",
472
+ wrap: true,
473
+ } as FlexText,
474
+ ],
475
+ margin: "lg",
476
+ paddingAll: "md",
477
+ backgroundColor: "#F8F9FA",
478
+ cornerRadius: "md",
479
+ } as FlexBox);
480
+ }
481
+
482
+ const bubble: FlexBubble = {
483
+ type: "bubble",
484
+ size: "mega",
485
+ body: {
486
+ type: "box",
487
+ layout: "vertical",
488
+ contents: headerContents,
489
+ paddingAll: "xl",
490
+ backgroundColor: "#FFFFFF",
491
+ },
492
+ };
493
+
494
+ if (imageUrl) {
495
+ bubble.hero = {
496
+ type: "image",
497
+ url: imageUrl,
498
+ size: "full",
499
+ aspectRatio: "16:9",
500
+ aspectMode: "cover",
501
+ } as FlexImage;
502
+ }
503
+
504
+ // Control buttons in refined grid layout (2 per row)
505
+ if (controls.length > 0) {
506
+ const rows: FlexComponent[] = [];
507
+ const limitedControls = controls.slice(0, 6);
508
+
509
+ for (let i = 0; i < limitedControls.length; i += 2) {
510
+ const rowButtons: FlexComponent[] = [];
511
+
512
+ for (let j = i; j < Math.min(i + 2, limitedControls.length); j++) {
513
+ const ctrl = limitedControls[j];
514
+ const buttonLabel = ctrl.icon ? `${ctrl.icon} ${ctrl.label}` : ctrl.label;
515
+
516
+ rowButtons.push({
517
+ type: "button",
518
+ action: {
519
+ type: "postback",
520
+ label: buttonLabel.slice(0, 18),
521
+ data: ctrl.data,
522
+ },
523
+ style: ctrl.style ?? "secondary",
524
+ flex: 1,
525
+ height: "sm",
526
+ margin: j > i ? "md" : undefined,
527
+ } as FlexButton);
528
+ }
529
+
530
+ // If odd number of controls in last row, add spacer
531
+ if (rowButtons.length === 1) {
532
+ rowButtons.push({
533
+ type: "filler",
534
+ });
535
+ }
536
+
537
+ rows.push({
538
+ type: "box",
539
+ layout: "horizontal",
540
+ contents: rowButtons,
541
+ margin: i > 0 ? "md" : undefined,
542
+ } as FlexBox);
543
+ }
544
+
545
+ bubble.footer = {
546
+ type: "box",
547
+ layout: "vertical",
548
+ contents: rows,
549
+ paddingAll: "lg",
550
+ backgroundColor: "#FAFAFA",
551
+ };
552
+ }
553
+
554
+ return bubble;
555
+ }
@@ -0,0 +1,13 @@
1
+ import type { messagingApi } from "@line/bot-sdk";
2
+ import type { FlexContainer } from "./types.js";
3
+
4
+ /**
5
+ * Wrap a FlexContainer in a FlexMessage
6
+ */
7
+ export function toFlexMessage(altText: string, contents: FlexContainer): messagingApi.FlexMessage {
8
+ return {
9
+ type: "flex",
10
+ altText,
11
+ contents,
12
+ };
13
+ }