@kodelyth/line 2026.5.42 → 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.
Files changed (90) hide show
  1. package/klaw.plugin.json +329 -2
  2. package/package.json +16 -4
  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,34 +0,0 @@
1
- import crypto from "node:crypto";
2
- import { afterEach, describe, expect, it, vi } from "vitest";
3
- import { validateLineSignature } from "./signature.js";
4
-
5
- function sign(body: string, secret: string): string {
6
- return crypto.createHmac("SHA256", secret).update(body).digest("base64");
7
- }
8
-
9
- describe("validateLineSignature", () => {
10
- afterEach(() => {
11
- vi.restoreAllMocks();
12
- });
13
-
14
- it("accepts a valid signature", () => {
15
- const body = JSON.stringify({ events: [{ type: "message" }] });
16
- const secret = "top-secret";
17
-
18
- expect(validateLineSignature(body, sign(body, secret), secret)).toBe(true);
19
- });
20
-
21
- it("still performs timing-safe comparison when signature length mismatches", () => {
22
- const body = JSON.stringify({ events: [{ type: "message" }] });
23
- const secret = "top-secret";
24
- const spy = vi.spyOn(crypto, "timingSafeEqual");
25
-
26
- expect(validateLineSignature(body, "short", secret)).toBe(false);
27
- expect(spy).toHaveBeenCalledTimes(1);
28
-
29
- const [left, right] = spy.mock.calls[0] ?? [];
30
- expect(left).toBeInstanceOf(Buffer);
31
- expect(right).toBeInstanceOf(Buffer);
32
- expect(left?.byteLength).toBe(right?.byteLength);
33
- });
34
- });
package/src/signature.ts DELETED
@@ -1,24 +0,0 @@
1
- import crypto from "node:crypto";
2
-
3
- export function validateLineSignature(
4
- body: string,
5
- signature: string,
6
- channelSecret: string,
7
- ): boolean {
8
- const hash = crypto.createHmac("SHA256", channelSecret).update(body).digest("base64");
9
- const hashBuffer = Buffer.from(hash);
10
- const signatureBuffer = Buffer.from(signature);
11
-
12
- // Pad to equal length before constant-time comparison to prevent
13
- // leaking length information via early-return timing.
14
- const maxLen = Math.max(hashBuffer.length, signatureBuffer.length);
15
- const paddedHash = Buffer.alloc(maxLen);
16
- const paddedSig = Buffer.alloc(maxLen);
17
- hashBuffer.copy(paddedHash);
18
- signatureBuffer.copy(paddedSig);
19
-
20
- // Call timingSafeEqual unconditionally to ensure constant-time execution
21
- // regardless of length mismatch (avoids && short-circuit timing leak).
22
- const timingResult = crypto.timingSafeEqual(paddedHash, paddedSig);
23
- return hashBuffer.length === signatureBuffer.length && timingResult;
24
- }
package/src/status.ts DELETED
@@ -1,37 +0,0 @@
1
- import { createLazyRuntimeModule } from "klaw/plugin-sdk/lazy-runtime";
2
- import {
3
- buildTokenChannelStatusSummary,
4
- createComputedAccountStatusAdapter,
5
- createDefaultChannelRuntimeState,
6
- createDependentCredentialStatusIssueCollector,
7
- } from "klaw/plugin-sdk/status-helpers";
8
- import { hasLineCredentials } from "./account-helpers.js";
9
- import { DEFAULT_ACCOUNT_ID, type ChannelPlugin, type ResolvedLineAccount } from "./channel-api.js";
10
-
11
- const loadLineProbeRuntime = createLazyRuntimeModule(() => import("./probe.runtime.js"));
12
-
13
- const collectLineStatusIssues = createDependentCredentialStatusIssueCollector({
14
- channel: "line",
15
- dependencySourceKey: "tokenSource",
16
- missingPrimaryMessage: "LINE channel access token not configured",
17
- missingDependentMessage: "LINE channel secret not configured",
18
- });
19
-
20
- export const lineStatusAdapter: NonNullable<ChannelPlugin<ResolvedLineAccount>["status"]> =
21
- createComputedAccountStatusAdapter<ResolvedLineAccount>({
22
- defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID),
23
- collectStatusIssues: collectLineStatusIssues,
24
- buildChannelSummary: ({ snapshot }) => buildTokenChannelStatusSummary(snapshot),
25
- probeAccount: async ({ account, timeoutMs }) =>
26
- await (await loadLineProbeRuntime()).probeLineBot(account.channelAccessToken, timeoutMs),
27
- resolveAccountSnapshot: ({ account }) => ({
28
- accountId: account.accountId,
29
- name: account.name,
30
- enabled: account.enabled,
31
- configured: hasLineCredentials(account),
32
- extra: {
33
- tokenSource: account.tokenSource,
34
- mode: "webhook",
35
- },
36
- }),
37
- });
@@ -1,333 +0,0 @@
1
- import type { messagingApi } from "@line/bot-sdk";
2
- import { messageAction, postbackAction, uriAction, type Action } from "./actions.js";
3
- import type { LineTemplateMessagePayload } from "./types.js";
4
-
5
- export { messageAction };
6
-
7
- type TemplateMessage = messagingApi.TemplateMessage;
8
- type ConfirmTemplate = messagingApi.ConfirmTemplate;
9
- type ButtonsTemplate = messagingApi.ButtonsTemplate;
10
- type CarouselTemplate = messagingApi.CarouselTemplate;
11
- type CarouselColumn = messagingApi.CarouselColumn;
12
- type ImageCarouselTemplate = messagingApi.ImageCarouselTemplate;
13
- type ImageCarouselColumn = messagingApi.ImageCarouselColumn;
14
-
15
- type TemplatePayloadAction = {
16
- type?: "uri" | "postback" | "message";
17
- uri?: string;
18
- data?: string;
19
- label: string;
20
- };
21
-
22
- function buildTemplatePayloadAction(action: TemplatePayloadAction): Action {
23
- if (action.type === "uri" && action.uri) {
24
- return uriAction(action.label, action.uri);
25
- }
26
- if (action.type === "postback" && action.data) {
27
- return postbackAction(action.label, action.data, action.label);
28
- }
29
- return messageAction(action.label, action.data ?? action.label);
30
- }
31
-
32
- /**
33
- * Create a confirm template (yes/no style dialog)
34
- */
35
- export function createConfirmTemplate(
36
- text: string,
37
- confirmAction: Action,
38
- cancelAction: Action,
39
- altText?: string,
40
- ): TemplateMessage {
41
- const template: ConfirmTemplate = {
42
- type: "confirm",
43
- text: text.slice(0, 240), // LINE limit
44
- actions: [confirmAction, cancelAction],
45
- };
46
-
47
- return {
48
- type: "template",
49
- altText: altText?.slice(0, 400) ?? text.slice(0, 400),
50
- template,
51
- };
52
- }
53
-
54
- /**
55
- * Create a button template with title, text, and action buttons
56
- */
57
- export function createButtonTemplate(
58
- title: string,
59
- text: string,
60
- actions: Action[],
61
- options?: {
62
- thumbnailImageUrl?: string;
63
- imageAspectRatio?: "rectangle" | "square";
64
- imageSize?: "cover" | "contain";
65
- imageBackgroundColor?: string;
66
- defaultAction?: Action;
67
- altText?: string;
68
- },
69
- ): TemplateMessage {
70
- const hasThumbnail = Boolean(options?.thumbnailImageUrl?.trim());
71
- const textLimit = hasThumbnail ? 160 : 60;
72
- const template: ButtonsTemplate = {
73
- type: "buttons",
74
- title: title.slice(0, 40), // LINE limit
75
- text: text.slice(0, textLimit), // LINE limit (60 if no thumbnail, 160 with thumbnail)
76
- actions: actions.slice(0, 4), // LINE limit: max 4 actions
77
- thumbnailImageUrl: options?.thumbnailImageUrl,
78
- imageAspectRatio: options?.imageAspectRatio ?? "rectangle",
79
- imageSize: options?.imageSize ?? "cover",
80
- imageBackgroundColor: options?.imageBackgroundColor,
81
- defaultAction: options?.defaultAction,
82
- };
83
-
84
- return {
85
- type: "template",
86
- altText: options?.altText?.slice(0, 400) ?? `${title}: ${text}`.slice(0, 400),
87
- template,
88
- };
89
- }
90
-
91
- /**
92
- * Create a carousel template with multiple columns
93
- */
94
- export function createTemplateCarousel(
95
- columns: CarouselColumn[],
96
- options?: {
97
- imageAspectRatio?: "rectangle" | "square";
98
- imageSize?: "cover" | "contain";
99
- altText?: string;
100
- },
101
- ): TemplateMessage {
102
- const template: CarouselTemplate = {
103
- type: "carousel",
104
- columns: columns.slice(0, 10), // LINE limit: max 10 columns
105
- imageAspectRatio: options?.imageAspectRatio ?? "rectangle",
106
- imageSize: options?.imageSize ?? "cover",
107
- };
108
-
109
- return {
110
- type: "template",
111
- altText: options?.altText?.slice(0, 400) ?? "View carousel",
112
- template,
113
- };
114
- }
115
-
116
- /**
117
- * Create a carousel column for use with createTemplateCarousel
118
- */
119
- export function createCarouselColumn(params: {
120
- title?: string;
121
- text: string;
122
- actions: Action[];
123
- thumbnailImageUrl?: string;
124
- imageBackgroundColor?: string;
125
- defaultAction?: Action;
126
- }): CarouselColumn {
127
- return {
128
- title: params.title?.slice(0, 40),
129
- text: params.text.slice(0, 120), // LINE limit
130
- actions: params.actions.slice(0, 3), // LINE limit: max 3 actions per column
131
- thumbnailImageUrl: params.thumbnailImageUrl,
132
- imageBackgroundColor: params.imageBackgroundColor,
133
- defaultAction: params.defaultAction,
134
- };
135
- }
136
-
137
- /**
138
- * Create an image carousel template (simpler, image-focused carousel)
139
- */
140
- export function createImageCarousel(
141
- columns: ImageCarouselColumn[],
142
- altText?: string,
143
- ): TemplateMessage {
144
- const template: ImageCarouselTemplate = {
145
- type: "image_carousel",
146
- columns: columns.slice(0, 10), // LINE limit: max 10 columns
147
- };
148
-
149
- return {
150
- type: "template",
151
- altText: altText?.slice(0, 400) ?? "View images",
152
- template,
153
- };
154
- }
155
-
156
- /**
157
- * Create an image carousel column for use with createImageCarousel
158
- */
159
- export function createImageCarouselColumn(imageUrl: string, action: Action): ImageCarouselColumn {
160
- return {
161
- imageUrl,
162
- action,
163
- };
164
- }
165
-
166
- /**
167
- * Create a simple yes/no confirmation dialog
168
- */
169
- export function createYesNoConfirm(
170
- question: string,
171
- options?: {
172
- yesText?: string;
173
- noText?: string;
174
- yesData?: string;
175
- noData?: string;
176
- altText?: string;
177
- },
178
- ): TemplateMessage {
179
- const yesAction: Action = options?.yesData
180
- ? postbackAction(options.yesText ?? "Yes", options.yesData, options.yesText ?? "Yes")
181
- : messageAction(options?.yesText ?? "Yes");
182
-
183
- const noAction: Action = options?.noData
184
- ? postbackAction(options.noText ?? "No", options.noData, options.noText ?? "No")
185
- : messageAction(options?.noText ?? "No");
186
-
187
- return createConfirmTemplate(question, yesAction, noAction, options?.altText);
188
- }
189
-
190
- /**
191
- * Create a button menu with simple text buttons
192
- */
193
- export function createButtonMenu(
194
- title: string,
195
- text: string,
196
- buttons: Array<{ label: string; text?: string }>,
197
- options?: {
198
- thumbnailImageUrl?: string;
199
- altText?: string;
200
- },
201
- ): TemplateMessage {
202
- const actions = buttons.slice(0, 4).map((btn) => messageAction(btn.label, btn.text));
203
-
204
- return createButtonTemplate(title, text, actions, {
205
- thumbnailImageUrl: options?.thumbnailImageUrl,
206
- altText: options?.altText,
207
- });
208
- }
209
-
210
- /**
211
- * Create a button menu with URL links
212
- */
213
- export function createLinkMenu(
214
- title: string,
215
- text: string,
216
- links: Array<{ label: string; url: string }>,
217
- options?: {
218
- thumbnailImageUrl?: string;
219
- altText?: string;
220
- },
221
- ): TemplateMessage {
222
- const actions = links.slice(0, 4).map((link) => uriAction(link.label, link.url));
223
-
224
- return createButtonTemplate(title, text, actions, {
225
- thumbnailImageUrl: options?.thumbnailImageUrl,
226
- altText: options?.altText,
227
- });
228
- }
229
-
230
- /**
231
- * Create a simple product/item carousel
232
- */
233
- export function createProductCarousel(
234
- products: Array<{
235
- title: string;
236
- description: string;
237
- imageUrl?: string;
238
- price?: string;
239
- actionLabel?: string;
240
- actionUrl?: string;
241
- actionData?: string;
242
- }>,
243
- altText?: string,
244
- ): TemplateMessage {
245
- const columns = products.slice(0, 10).map((product) => {
246
- const actions: Action[] = [];
247
-
248
- if (product.actionUrl) {
249
- actions.push(uriAction(product.actionLabel ?? "View", product.actionUrl));
250
- } else if (product.actionData) {
251
- actions.push(postbackAction(product.actionLabel ?? "Select", product.actionData));
252
- } else {
253
- actions.push(messageAction(product.actionLabel ?? "Select", product.title));
254
- }
255
-
256
- return createCarouselColumn({
257
- title: product.title,
258
- text: product.price
259
- ? `${product.description}\n${product.price}`.slice(0, 120)
260
- : product.description,
261
- thumbnailImageUrl: product.imageUrl,
262
- actions,
263
- });
264
- });
265
-
266
- return createTemplateCarousel(columns, { altText });
267
- }
268
-
269
- /**
270
- * Convert a TemplateMessagePayload from ReplyPayload to a LINE TemplateMessage
271
- */
272
- export function buildTemplateMessageFromPayload(
273
- payload: LineTemplateMessagePayload,
274
- ): TemplateMessage | null {
275
- switch (payload.type) {
276
- case "confirm": {
277
- const confirmAction = payload.confirmData.startsWith("http")
278
- ? uriAction(payload.confirmLabel, payload.confirmData)
279
- : payload.confirmData.includes("=")
280
- ? postbackAction(payload.confirmLabel, payload.confirmData, payload.confirmLabel)
281
- : messageAction(payload.confirmLabel, payload.confirmData);
282
-
283
- const cancelAction = payload.cancelData.startsWith("http")
284
- ? uriAction(payload.cancelLabel, payload.cancelData)
285
- : payload.cancelData.includes("=")
286
- ? postbackAction(payload.cancelLabel, payload.cancelData, payload.cancelLabel)
287
- : messageAction(payload.cancelLabel, payload.cancelData);
288
-
289
- return createConfirmTemplate(payload.text, confirmAction, cancelAction, payload.altText);
290
- }
291
-
292
- case "buttons": {
293
- const actions: Action[] = payload.actions
294
- .slice(0, 4)
295
- .map((action) => buildTemplatePayloadAction(action));
296
-
297
- return createButtonTemplate(payload.title, payload.text, actions, {
298
- thumbnailImageUrl: payload.thumbnailImageUrl,
299
- altText: payload.altText,
300
- });
301
- }
302
-
303
- case "carousel": {
304
- const columns: CarouselColumn[] = payload.columns.slice(0, 10).map((col) => {
305
- const colActions: Action[] = col.actions
306
- .slice(0, 3)
307
- .map((action) => buildTemplatePayloadAction(action));
308
-
309
- return createCarouselColumn({
310
- title: col.title,
311
- text: col.text,
312
- thumbnailImageUrl: col.thumbnailImageUrl,
313
- actions: colActions,
314
- });
315
- });
316
-
317
- return createTemplateCarousel(columns, { altText: payload.altText });
318
- }
319
-
320
- default:
321
- return null;
322
- }
323
- }
324
-
325
- export type {
326
- TemplateMessage,
327
- ConfirmTemplate,
328
- ButtonsTemplate,
329
- CarouselTemplate,
330
- CarouselColumn,
331
- ImageCarouselTemplate,
332
- ImageCarouselColumn,
333
- };
package/src/types.ts DELETED
@@ -1,130 +0,0 @@
1
- import type { BaseProbeResult } from "klaw/plugin-sdk/channel-contract";
2
- import type { MessageReceipt } from "klaw/plugin-sdk/channel-message";
3
-
4
- export type LineTokenSource = "config" | "env" | "file" | "none";
5
-
6
- interface LineThreadBindingsConfig {
7
- enabled?: boolean;
8
- idleHours?: number;
9
- maxAgeHours?: number;
10
- spawnSessions?: boolean;
11
- defaultSpawnContext?: "isolated" | "fork";
12
- /** @deprecated Use spawnSessions instead. */
13
- spawnSubagentSessions?: boolean;
14
- /** @deprecated Use spawnSessions instead. */
15
- spawnAcpSessions?: boolean;
16
- }
17
-
18
- interface LineAccountBaseConfig {
19
- enabled?: boolean;
20
- channelAccessToken?: string;
21
- channelSecret?: string;
22
- tokenFile?: string;
23
- secretFile?: string;
24
- name?: string;
25
- allowFrom?: Array<string | number>;
26
- groupAllowFrom?: Array<string | number>;
27
- dmPolicy?: "open" | "allowlist" | "pairing" | "disabled";
28
- groupPolicy?: "open" | "allowlist" | "disabled";
29
- responsePrefix?: string;
30
- mediaMaxMb?: number;
31
- webhookPath?: string;
32
- threadBindings?: LineThreadBindingsConfig;
33
- groups?: Record<string, LineGroupConfig>;
34
- }
35
-
36
- export interface LineConfig extends LineAccountBaseConfig {
37
- accounts?: Record<string, LineAccountConfig>;
38
- defaultAccount?: string;
39
- }
40
-
41
- export interface LineAccountConfig extends LineAccountBaseConfig {}
42
-
43
- export interface LineGroupConfig {
44
- enabled?: boolean;
45
- allowFrom?: Array<string | number>;
46
- requireMention?: boolean;
47
- systemPrompt?: string;
48
- skills?: string[];
49
- }
50
-
51
- export interface ResolvedLineAccount {
52
- accountId: string;
53
- name?: string;
54
- enabled: boolean;
55
- channelAccessToken: string;
56
- channelSecret: string;
57
- tokenSource: LineTokenSource;
58
- config: LineConfig & LineAccountConfig;
59
- }
60
-
61
- export interface LineSendResult {
62
- messageId: string;
63
- chatId: string;
64
- receipt: MessageReceipt;
65
- }
66
-
67
- export type LineProbeResult = BaseProbeResult<string> & {
68
- bot?: {
69
- displayName?: string;
70
- userId?: string;
71
- basicId?: string;
72
- pictureUrl?: string;
73
- };
74
- };
75
-
76
- type LineFlexMessagePayload = {
77
- altText: string;
78
- contents: unknown;
79
- };
80
-
81
- export type LineTemplateMessagePayload =
82
- | {
83
- type: "confirm";
84
- text: string;
85
- confirmLabel: string;
86
- confirmData: string;
87
- cancelLabel: string;
88
- cancelData: string;
89
- altText?: string;
90
- }
91
- | {
92
- type: "buttons";
93
- title: string;
94
- text: string;
95
- actions: Array<{
96
- type: "message" | "uri" | "postback";
97
- label: string;
98
- data?: string;
99
- uri?: string;
100
- }>;
101
- thumbnailImageUrl?: string;
102
- altText?: string;
103
- }
104
- | {
105
- type: "carousel";
106
- columns: Array<{
107
- title?: string;
108
- text: string;
109
- thumbnailImageUrl?: string;
110
- actions: Array<{
111
- type: "message" | "uri" | "postback";
112
- label: string;
113
- data?: string;
114
- uri?: string;
115
- }>;
116
- }>;
117
- altText?: string;
118
- };
119
-
120
- export type LineChannelData = {
121
- quickReplies?: string[];
122
- location?: {
123
- title: string;
124
- address: string;
125
- latitude: number;
126
- longitude: number;
127
- };
128
- flexMessage?: LineFlexMessagePayload;
129
- templateMessage?: LineTemplateMessagePayload;
130
- };