@openclaw/line 2026.5.2-beta.2 → 2026.5.3-beta.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 (109) hide show
  1. package/dist/accounts-UXAvdBo_.js +105 -0
  2. package/dist/api.js +11 -0
  3. package/dist/basic-cards-7DTG6E-A.js +307 -0
  4. package/dist/card-command-UlPc6Vuc.js +240 -0
  5. package/dist/channel-Rls9s60K.js +601 -0
  6. package/dist/channel-plugin-api.js +2 -0
  7. package/dist/channel.runtime-bXoj8_Sl.js +4 -0
  8. package/dist/contract-api.js +2 -0
  9. package/dist/index.js +45 -0
  10. package/dist/markdown-to-line-CCWK6PJh.js +789 -0
  11. package/dist/monitor-B4_RxHc0.js +1523 -0
  12. package/dist/monitor.runtime-DyxYlrM9.js +2 -0
  13. package/dist/outbound-media-BfhBmWq5.js +55 -0
  14. package/dist/outbound.runtime-B-luYwEB.js +2 -0
  15. package/dist/probe-D9BPzb2n.js +30 -0
  16. package/dist/probe.runtime-N91OWW1J.js +2 -0
  17. package/dist/quick-reply-fallback-HskUUWsn.js +55 -0
  18. package/dist/reply-payload-transform-CFGTs9ln.js +713 -0
  19. package/dist/runtime-api.js +277 -0
  20. package/dist/schedule-cards-BwQChTdM.js +359 -0
  21. package/dist/secret-contract-api.js +5 -0
  22. package/dist/setup-api.js +2 -0
  23. package/dist/setup-entry.js +11 -0
  24. package/dist/setup-surface-DCH7CE-y.js +281 -0
  25. package/package.json +14 -6
  26. package/api.ts +0 -11
  27. package/channel-plugin-api.ts +0 -1
  28. package/contract-api.ts +0 -5
  29. package/index.ts +0 -54
  30. package/runtime-api.ts +0 -187
  31. package/secret-contract-api.ts +0 -4
  32. package/setup-api.ts +0 -2
  33. package/setup-entry.ts +0 -9
  34. package/src/account-helpers.ts +0 -16
  35. package/src/accounts.test.ts +0 -290
  36. package/src/accounts.ts +0 -187
  37. package/src/actions.ts +0 -61
  38. package/src/auto-reply-delivery.test.ts +0 -248
  39. package/src/auto-reply-delivery.ts +0 -200
  40. package/src/bindings.ts +0 -65
  41. package/src/bot-access.ts +0 -48
  42. package/src/bot-handlers.test.ts +0 -1089
  43. package/src/bot-handlers.ts +0 -642
  44. package/src/bot-message-context.test.ts +0 -405
  45. package/src/bot-message-context.ts +0 -581
  46. package/src/bot.ts +0 -70
  47. package/src/card-command.ts +0 -347
  48. package/src/channel-access-token.ts +0 -14
  49. package/src/channel-api.ts +0 -17
  50. package/src/channel-setup-status.contract.test.ts +0 -70
  51. package/src/channel-shared.ts +0 -48
  52. package/src/channel.logout.test.ts +0 -145
  53. package/src/channel.runtime.ts +0 -3
  54. package/src/channel.sendPayload.test.ts +0 -514
  55. package/src/channel.setup.ts +0 -11
  56. package/src/channel.status.test.ts +0 -63
  57. package/src/channel.ts +0 -154
  58. package/src/config-adapter.ts +0 -29
  59. package/src/config-schema.ts +0 -57
  60. package/src/download.test.ts +0 -133
  61. package/src/download.ts +0 -87
  62. package/src/flex-templates/basic-cards.ts +0 -395
  63. package/src/flex-templates/common.ts +0 -20
  64. package/src/flex-templates/media-control-cards.ts +0 -555
  65. package/src/flex-templates/message.ts +0 -13
  66. package/src/flex-templates/schedule-cards.ts +0 -467
  67. package/src/flex-templates/types.ts +0 -22
  68. package/src/flex-templates.ts +0 -32
  69. package/src/gateway.ts +0 -129
  70. package/src/group-keys.test.ts +0 -123
  71. package/src/group-keys.ts +0 -65
  72. package/src/group-policy.ts +0 -22
  73. package/src/markdown-to-line.test.ts +0 -348
  74. package/src/markdown-to-line.ts +0 -416
  75. package/src/message-cards.test.ts +0 -204
  76. package/src/monitor.lifecycle.test.ts +0 -421
  77. package/src/monitor.runtime.ts +0 -1
  78. package/src/monitor.ts +0 -506
  79. package/src/outbound-media.test.ts +0 -189
  80. package/src/outbound-media.ts +0 -120
  81. package/src/outbound.runtime.ts +0 -12
  82. package/src/outbound.ts +0 -356
  83. package/src/probe.contract.test.ts +0 -9
  84. package/src/probe.runtime.ts +0 -1
  85. package/src/probe.ts +0 -34
  86. package/src/quick-reply-fallback.ts +0 -10
  87. package/src/reply-chunks.test.ts +0 -179
  88. package/src/reply-chunks.ts +0 -110
  89. package/src/reply-payload-transform.test.ts +0 -387
  90. package/src/reply-payload-transform.ts +0 -317
  91. package/src/rich-menu.test.ts +0 -310
  92. package/src/rich-menu.ts +0 -326
  93. package/src/runtime.ts +0 -32
  94. package/src/send.test.ts +0 -346
  95. package/src/send.ts +0 -489
  96. package/src/setup-core.ts +0 -149
  97. package/src/setup-runtime-api.ts +0 -9
  98. package/src/setup-surface.test.ts +0 -474
  99. package/src/setup-surface.ts +0 -227
  100. package/src/signature.test.ts +0 -34
  101. package/src/signature.ts +0 -24
  102. package/src/status.ts +0 -37
  103. package/src/template-messages.ts +0 -333
  104. package/src/types.ts +0 -128
  105. package/src/webhook-node.test.ts +0 -513
  106. package/src/webhook-node.ts +0 -131
  107. package/src/webhook-utils.ts +0 -10
  108. package/src/webhook.ts +0 -111
  109. package/tsconfig.json +0 -16
@@ -1,581 +0,0 @@
1
- import type { webhook } from "@line/bot-sdk";
2
- import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
3
- import {
4
- formatInboundEnvelope,
5
- formatLocationText,
6
- resolveInboundSessionEnvelopeContext,
7
- toLocationContext,
8
- } from "openclaw/plugin-sdk/channel-inbound";
9
- import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
10
- import {
11
- ensureConfiguredBindingRouteReady,
12
- resolvePinnedMainDmOwnerFromAllowlist,
13
- resolveConfiguredBindingRoute,
14
- resolveRuntimeConversationBindingRoute,
15
- } from "openclaw/plugin-sdk/conversation-runtime";
16
- import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
17
- import type { HistoryEntry } from "openclaw/plugin-sdk/reply-history";
18
- import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
19
- import { logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
20
- import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
21
- import { normalizeAllowFrom } from "./bot-access.js";
22
- import { resolveLineGroupConfigEntry } from "./group-keys.js";
23
- import type { ResolvedLineAccount } from "./types.js";
24
-
25
- type EventSource = webhook.Source | undefined;
26
- type MessageEvent = webhook.MessageEvent;
27
- type PostbackEvent = webhook.PostbackEvent;
28
- type StickerEventMessage = webhook.StickerMessageContent;
29
-
30
- interface MediaRef {
31
- path: string;
32
- contentType?: string;
33
- }
34
-
35
- interface BuildLineMessageContextParams {
36
- event: MessageEvent;
37
- allMedia: MediaRef[];
38
- cfg: OpenClawConfig;
39
- account: ResolvedLineAccount;
40
- commandAuthorized: boolean;
41
- groupHistories?: Map<string, HistoryEntry[]>;
42
- historyLimit?: number;
43
- }
44
-
45
- type LineSourceInfo = {
46
- userId?: string;
47
- groupId?: string;
48
- roomId?: string;
49
- isGroup: boolean;
50
- };
51
-
52
- export function getLineSourceInfo(source: EventSource): LineSourceInfo {
53
- if (!source) {
54
- return { userId: undefined, groupId: undefined, roomId: undefined, isGroup: false };
55
- }
56
- const userId =
57
- source.type === "user"
58
- ? source.userId
59
- : source.type === "group"
60
- ? source.userId
61
- : source.type === "room"
62
- ? source.userId
63
- : undefined;
64
- const groupId = source.type === "group" ? source.groupId : undefined;
65
- const roomId = source.type === "room" ? source.roomId : undefined;
66
- const isGroup = source.type === "group" || source.type === "room";
67
-
68
- return { userId, groupId, roomId, isGroup };
69
- }
70
-
71
- function buildPeerId(source: EventSource): string {
72
- if (!source) {
73
- return "unknown";
74
- }
75
- const groupKey =
76
- normalizeOptionalString(source.type === "group" ? source.groupId : undefined) ??
77
- normalizeOptionalString(source.type === "room" ? source.roomId : undefined);
78
- if (groupKey) {
79
- return groupKey;
80
- }
81
- if (source.type === "user" && source.userId) {
82
- return source.userId;
83
- }
84
- return "unknown";
85
- }
86
-
87
- async function resolveLineInboundRoute(params: {
88
- source: EventSource;
89
- cfg: OpenClawConfig;
90
- account: ResolvedLineAccount;
91
- }): Promise<{
92
- userId?: string;
93
- groupId?: string;
94
- roomId?: string;
95
- isGroup: boolean;
96
- peerId: string;
97
- route: ReturnType<typeof resolveAgentRoute>;
98
- }> {
99
- recordChannelActivity({
100
- channel: "line",
101
- accountId: params.account.accountId,
102
- direction: "inbound",
103
- });
104
-
105
- const { userId, groupId, roomId, isGroup } = getLineSourceInfo(params.source);
106
- const peerId = buildPeerId(params.source);
107
- let route = resolveAgentRoute({
108
- cfg: params.cfg,
109
- channel: "line",
110
- accountId: params.account.accountId,
111
- peer: {
112
- kind: isGroup ? "group" : "direct",
113
- id: peerId,
114
- },
115
- });
116
-
117
- const configuredRoute = resolveConfiguredBindingRoute({
118
- cfg: params.cfg,
119
- route,
120
- conversation: {
121
- channel: "line",
122
- accountId: params.account.accountId,
123
- conversationId: peerId,
124
- },
125
- });
126
- let configuredBinding = configuredRoute.bindingResolution;
127
- const configuredBindingSessionKey = configuredRoute.boundSessionKey ?? "";
128
- route = configuredRoute.route;
129
-
130
- const runtimeRoute = resolveRuntimeConversationBindingRoute({
131
- route,
132
- conversation: {
133
- channel: "line",
134
- accountId: params.account.accountId,
135
- conversationId: peerId,
136
- },
137
- });
138
- route = runtimeRoute.route;
139
- if (runtimeRoute.bindingRecord) {
140
- configuredBinding = null;
141
- logVerbose(
142
- runtimeRoute.boundSessionKey
143
- ? `line: routed via bound conversation ${peerId} -> ${runtimeRoute.boundSessionKey}`
144
- : `line: plugin-bound conversation ${peerId}`,
145
- );
146
- }
147
-
148
- if (configuredBinding) {
149
- const ensured = await ensureConfiguredBindingRouteReady({
150
- cfg: params.cfg,
151
- bindingResolution: configuredBinding,
152
- });
153
- if (!ensured.ok) {
154
- logVerbose(
155
- `line: configured ACP binding unavailable for ${peerId} -> ${configuredBindingSessionKey}: ${ensured.error}`,
156
- );
157
- throw new Error(`Configured ACP binding unavailable: ${ensured.error}`);
158
- }
159
- logVerbose(
160
- `line: using configured ACP binding for ${peerId} -> ${configuredBindingSessionKey}`,
161
- );
162
- }
163
-
164
- return { userId, groupId, roomId, isGroup, peerId, route };
165
- }
166
-
167
- const STICKER_PACKAGES: Record<string, string> = {
168
- "1": "Moon & James",
169
- "2": "Cony & Brown",
170
- "3": "Brown & Friends",
171
- "4": "Moon Special",
172
- "789": "LINE Characters",
173
- "6136": "Cony's Happy Life",
174
- "6325": "Brown's Life",
175
- "6359": "Choco",
176
- "6362": "Sally",
177
- "6370": "Edward",
178
- "11537": "Cony",
179
- "11538": "Brown",
180
- "11539": "Moon",
181
- };
182
-
183
- function describeStickerKeywords(sticker: StickerEventMessage): string {
184
- const keywords = (sticker as StickerEventMessage & { keywords?: string[] }).keywords;
185
- if (keywords && keywords.length > 0) {
186
- return keywords.slice(0, 3).join(", ");
187
- }
188
-
189
- const stickerText = (sticker as StickerEventMessage & { text?: string }).text;
190
- if (stickerText) {
191
- return stickerText;
192
- }
193
-
194
- return "";
195
- }
196
-
197
- function extractMessageText(message: MessageEvent["message"]): string {
198
- if (message.type === "text") {
199
- return message.text;
200
- }
201
- if (message.type === "location") {
202
- const loc = message;
203
- return (
204
- formatLocationText({
205
- latitude: loc.latitude,
206
- longitude: loc.longitude,
207
- name: loc.title,
208
- address: loc.address,
209
- }) ?? ""
210
- );
211
- }
212
- if (message.type === "sticker") {
213
- const sticker = message;
214
- const packageName = STICKER_PACKAGES[sticker.packageId] ?? "sticker";
215
- const keywords = describeStickerKeywords(sticker);
216
-
217
- if (keywords) {
218
- return `[Sent a ${packageName} sticker: ${keywords}]`;
219
- }
220
- return `[Sent a ${packageName} sticker]`;
221
- }
222
- return "";
223
- }
224
-
225
- function extractMediaPlaceholder(message: MessageEvent["message"]): string {
226
- switch (message.type) {
227
- case "image":
228
- return "<media:image>";
229
- case "video":
230
- return "<media:video>";
231
- case "audio":
232
- return "<media:audio>";
233
- case "file":
234
- return "<media:document>";
235
- default:
236
- return "";
237
- }
238
- }
239
-
240
- type LineRouteInfo = ReturnType<typeof resolveAgentRoute>;
241
- type LineSourceInfoWithPeerId = LineSourceInfo & { peerId: string };
242
-
243
- function resolveLineConversationLabel(params: {
244
- isGroup: boolean;
245
- groupId?: string;
246
- roomId?: string;
247
- senderLabel: string;
248
- }): string {
249
- return params.isGroup
250
- ? params.groupId
251
- ? `group:${params.groupId}`
252
- : params.roomId
253
- ? `room:${params.roomId}`
254
- : "unknown-group"
255
- : params.senderLabel;
256
- }
257
-
258
- function resolveLineAddresses(params: {
259
- isGroup: boolean;
260
- groupId?: string;
261
- roomId?: string;
262
- userId?: string;
263
- peerId: string;
264
- }): { fromAddress: string; toAddress: string; originatingTo: string } {
265
- const fromAddress = params.isGroup
266
- ? params.groupId
267
- ? `line:group:${params.groupId}`
268
- : params.roomId
269
- ? `line:room:${params.roomId}`
270
- : `line:${params.peerId}`
271
- : `line:${params.userId ?? params.peerId}`;
272
- const toAddress = params.isGroup ? fromAddress : `line:${params.userId ?? params.peerId}`;
273
- const originatingTo = params.isGroup ? fromAddress : `line:${params.userId ?? params.peerId}`;
274
- return { fromAddress, toAddress, originatingTo };
275
- }
276
-
277
- async function finalizeLineInboundContext(params: {
278
- cfg: OpenClawConfig;
279
- account: ResolvedLineAccount;
280
- event: MessageEvent | PostbackEvent;
281
- route: LineRouteInfo;
282
- source: LineSourceInfoWithPeerId;
283
- rawBody: string;
284
- timestamp: number;
285
- messageSid: string;
286
- commandAuthorized: boolean;
287
- media: {
288
- firstPath: string | undefined;
289
- firstContentType?: string;
290
- paths?: string[];
291
- types?: string[];
292
- };
293
- locationContext?: ReturnType<typeof toLocationContext>;
294
- verboseLog: { kind: "inbound" | "postback"; mediaCount?: number };
295
- inboundHistory?: Pick<HistoryEntry, "sender" | "body" | "timestamp">[];
296
- }) {
297
- const { fromAddress, toAddress, originatingTo } = resolveLineAddresses({
298
- isGroup: params.source.isGroup,
299
- groupId: params.source.groupId,
300
- roomId: params.source.roomId,
301
- userId: params.source.userId,
302
- peerId: params.source.peerId,
303
- });
304
-
305
- const senderId = params.source.userId ?? "unknown";
306
- const senderLabel = params.source.userId ? `user:${params.source.userId}` : "unknown";
307
- const conversationLabel = resolveLineConversationLabel({
308
- isGroup: params.source.isGroup,
309
- groupId: params.source.groupId,
310
- roomId: params.source.roomId,
311
- senderLabel,
312
- });
313
-
314
- const { storePath, envelopeOptions, previousTimestamp } = resolveInboundSessionEnvelopeContext({
315
- cfg: params.cfg,
316
- agentId: params.route.agentId,
317
- sessionKey: params.route.sessionKey,
318
- });
319
-
320
- const body = formatInboundEnvelope({
321
- channel: "LINE",
322
- from: conversationLabel,
323
- timestamp: params.timestamp,
324
- body: params.rawBody,
325
- chatType: params.source.isGroup ? "group" : "direct",
326
- sender: {
327
- id: senderId,
328
- },
329
- previousTimestamp,
330
- envelope: envelopeOptions,
331
- });
332
-
333
- const ctxPayload = finalizeInboundContext({
334
- Body: body,
335
- BodyForAgent: params.rawBody,
336
- RawBody: params.rawBody,
337
- CommandBody: params.rawBody,
338
- From: fromAddress,
339
- To: toAddress,
340
- SessionKey: params.route.sessionKey,
341
- AccountId: params.route.accountId,
342
- ChatType: params.source.isGroup ? "group" : "direct",
343
- ConversationLabel: conversationLabel,
344
- GroupSubject: params.source.isGroup
345
- ? (params.source.groupId ?? params.source.roomId)
346
- : undefined,
347
- SenderId: senderId,
348
- Provider: "line",
349
- Surface: "line",
350
- MessageSid: params.messageSid,
351
- Timestamp: params.timestamp,
352
- MediaPath: params.media.firstPath,
353
- MediaType: params.media.firstContentType,
354
- MediaUrl: params.media.firstPath,
355
- MediaPaths: params.media.paths,
356
- MediaUrls: params.media.paths,
357
- MediaTypes: params.media.types,
358
- ...params.locationContext,
359
- CommandAuthorized: params.commandAuthorized,
360
- OriginatingChannel: "line" as const,
361
- OriginatingTo: originatingTo,
362
- GroupSystemPrompt: params.source.isGroup
363
- ? normalizeOptionalString(
364
- resolveLineGroupConfigEntry(params.account.config.groups, {
365
- groupId: params.source.groupId,
366
- roomId: params.source.roomId,
367
- })?.systemPrompt,
368
- )
369
- : undefined,
370
- InboundHistory: params.inboundHistory,
371
- });
372
-
373
- const pinnedMainDmOwner = !params.source.isGroup
374
- ? resolvePinnedMainDmOwnerFromAllowlist({
375
- dmScope: params.cfg.session?.dmScope,
376
- allowFrom: params.account.config.allowFrom,
377
- normalizeEntry: (entry) => normalizeAllowFrom([entry]).entries[0],
378
- })
379
- : null;
380
- if (shouldLogVerbose()) {
381
- const preview = body.slice(0, 200).replace(/\n/g, "\\n");
382
- const mediaInfo =
383
- params.verboseLog.kind === "inbound" && (params.verboseLog.mediaCount ?? 0) > 1
384
- ? ` mediaCount=${params.verboseLog.mediaCount}`
385
- : "";
386
- const label = params.verboseLog.kind === "inbound" ? "line inbound" : "line postback";
387
- logVerbose(
388
- `${label}: from=${ctxPayload.From} len=${body.length}${mediaInfo} preview="${preview}"`,
389
- );
390
- }
391
-
392
- return {
393
- ctxPayload,
394
- replyToken: (params.event as { replyToken: string }).replyToken,
395
- turn: {
396
- storePath,
397
- record: {
398
- updateLastRoute: !params.source.isGroup
399
- ? {
400
- sessionKey: params.route.mainSessionKey,
401
- channel: "line",
402
- to: params.source.userId ?? params.source.peerId,
403
- accountId: params.route.accountId,
404
- mainDmOwnerPin:
405
- pinnedMainDmOwner && params.source.userId
406
- ? {
407
- ownerRecipient: pinnedMainDmOwner,
408
- senderRecipient: params.source.userId,
409
- onSkip: ({
410
- ownerRecipient,
411
- senderRecipient,
412
- }: {
413
- ownerRecipient: string;
414
- senderRecipient: string;
415
- }) => {
416
- logVerbose(
417
- `line: skip main-session last route for ${senderRecipient} (pinned owner ${ownerRecipient})`,
418
- );
419
- },
420
- }
421
- : undefined,
422
- }
423
- : undefined,
424
- onRecordError: (err: unknown) => {
425
- logVerbose(`line: failed updating session meta: ${String(err)}`);
426
- },
427
- },
428
- },
429
- };
430
- }
431
-
432
- export async function buildLineMessageContext(params: BuildLineMessageContextParams) {
433
- const { event, allMedia, cfg, account, commandAuthorized, groupHistories, historyLimit } = params;
434
-
435
- const source = event.source;
436
- const { userId, groupId, roomId, isGroup, peerId, route } = await resolveLineInboundRoute({
437
- source,
438
- cfg,
439
- account,
440
- });
441
-
442
- const message = event.message;
443
- const messageId = message.id;
444
- const timestamp = event.timestamp;
445
-
446
- const textContent = extractMessageText(message);
447
- const placeholder = extractMediaPlaceholder(message);
448
-
449
- let rawBody = textContent || placeholder;
450
- if (!rawBody && allMedia.length > 0) {
451
- rawBody = `<media:image>${allMedia.length > 1 ? ` (${allMedia.length} images)` : ""}`;
452
- }
453
-
454
- if (!rawBody && allMedia.length === 0) {
455
- return null;
456
- }
457
-
458
- let locationContext: ReturnType<typeof toLocationContext> | undefined;
459
- if (message.type === "location") {
460
- const loc = message;
461
- locationContext = toLocationContext({
462
- latitude: loc.latitude,
463
- longitude: loc.longitude,
464
- name: loc.title,
465
- address: loc.address,
466
- });
467
- }
468
-
469
- const historyKey = isGroup ? peerId : undefined;
470
- const inboundHistory =
471
- historyKey && groupHistories && (historyLimit ?? 0) > 0
472
- ? (groupHistories.get(historyKey) ?? []).map((entry) => ({
473
- sender: entry.sender,
474
- body: entry.body,
475
- timestamp: entry.timestamp,
476
- }))
477
- : undefined;
478
-
479
- const finalized = await finalizeLineInboundContext({
480
- cfg,
481
- account,
482
- event,
483
- route,
484
- source: { userId, groupId, roomId, isGroup, peerId },
485
- rawBody,
486
- timestamp,
487
- messageSid: messageId,
488
- commandAuthorized,
489
- media: {
490
- firstPath: allMedia[0]?.path,
491
- firstContentType: allMedia[0]?.contentType,
492
- paths: allMedia.length > 0 ? allMedia.map((m) => m.path) : undefined,
493
- types:
494
- allMedia.length > 0
495
- ? (allMedia.map((m) => m.contentType).filter(Boolean) as string[])
496
- : undefined,
497
- },
498
- locationContext,
499
- verboseLog: { kind: "inbound", mediaCount: allMedia.length },
500
- inboundHistory,
501
- });
502
-
503
- return {
504
- ctxPayload: finalized.ctxPayload,
505
- turn: finalized.turn,
506
- event,
507
- userId,
508
- groupId,
509
- roomId,
510
- isGroup,
511
- route,
512
- replyToken: event.replyToken,
513
- accountId: account.accountId,
514
- };
515
- }
516
-
517
- export async function buildLinePostbackContext(params: {
518
- event: PostbackEvent;
519
- cfg: OpenClawConfig;
520
- account: ResolvedLineAccount;
521
- commandAuthorized: boolean;
522
- }) {
523
- const { event, cfg, account, commandAuthorized } = params;
524
-
525
- const source = event.source;
526
- const { userId, groupId, roomId, isGroup, peerId, route } = await resolveLineInboundRoute({
527
- source,
528
- cfg,
529
- account,
530
- });
531
-
532
- const timestamp = event.timestamp;
533
- const rawData = event.postback?.data?.trim() ?? "";
534
- if (!rawData) {
535
- return null;
536
- }
537
- let rawBody = rawData;
538
- if (rawData.includes("line.action=")) {
539
- const searchParams = new URLSearchParams(rawData);
540
- const action = searchParams.get("line.action") ?? "";
541
- const device = searchParams.get("line.device");
542
- rawBody = device ? `line action ${action} device ${device}` : `line action ${action}`;
543
- }
544
-
545
- const messageSid = event.replyToken ? `postback:${event.replyToken}` : `postback:${timestamp}`;
546
- const finalized = await finalizeLineInboundContext({
547
- cfg,
548
- account,
549
- event,
550
- route,
551
- source: { userId, groupId, roomId, isGroup, peerId },
552
- rawBody,
553
- timestamp,
554
- messageSid,
555
- commandAuthorized,
556
- media: {
557
- firstPath: "",
558
- firstContentType: undefined,
559
- paths: undefined,
560
- types: undefined,
561
- },
562
- verboseLog: { kind: "postback" },
563
- });
564
-
565
- return {
566
- ctxPayload: finalized.ctxPayload,
567
- turn: finalized.turn,
568
- event,
569
- userId,
570
- groupId,
571
- roomId,
572
- isGroup,
573
- route,
574
- replyToken: event.replyToken,
575
- accountId: account.accountId,
576
- };
577
- }
578
-
579
- type LineMessageContext = NonNullable<Awaited<ReturnType<typeof buildLineMessageContext>>>;
580
- type LinePostbackContext = NonNullable<Awaited<ReturnType<typeof buildLinePostbackContext>>>;
581
- export type LineInboundContext = LineMessageContext | LinePostbackContext;
package/src/bot.ts DELETED
@@ -1,70 +0,0 @@
1
- import type { webhook } from "@line/bot-sdk";
2
- import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
3
- import { DEFAULT_GROUP_HISTORY_LIMIT, type HistoryEntry } from "openclaw/plugin-sdk/reply-history";
4
- import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
5
- import {
6
- createNonExitingRuntime,
7
- logVerbose,
8
- type RuntimeEnv,
9
- } from "openclaw/plugin-sdk/runtime-env";
10
- import { resolveLineAccount } from "./accounts.js";
11
- import { createLineWebhookReplayCache, handleLineWebhookEvents } from "./bot-handlers.js";
12
- import type { LineInboundContext } from "./bot-message-context.js";
13
- import type { ResolvedLineAccount } from "./types.js";
14
-
15
- interface LineBotOptions {
16
- channelAccessToken: string;
17
- channelSecret: string;
18
- accountId?: string;
19
- runtime?: RuntimeEnv;
20
- config?: OpenClawConfig;
21
- mediaMaxMb?: number;
22
- onMessage?: (ctx: LineInboundContext) => Promise<void>;
23
- }
24
-
25
- interface LineBot {
26
- handleWebhook: (body: webhook.CallbackRequest) => Promise<void>;
27
- account: ResolvedLineAccount;
28
- }
29
-
30
- export function createLineBot(opts: LineBotOptions): LineBot {
31
- const runtime: RuntimeEnv = opts.runtime ?? createNonExitingRuntime();
32
-
33
- const cfg = opts.config ?? getRuntimeConfig();
34
- const account = resolveLineAccount({
35
- cfg,
36
- accountId: opts.accountId,
37
- });
38
-
39
- const mediaMaxBytes = (opts.mediaMaxMb ?? account.config.mediaMaxMb ?? 10) * 1024 * 1024;
40
-
41
- const processMessage =
42
- opts.onMessage ??
43
- (async () => {
44
- logVerbose("line: no message handler configured");
45
- });
46
- const replayCache = createLineWebhookReplayCache();
47
- const groupHistories = new Map<string, HistoryEntry[]>();
48
-
49
- const handleWebhook = async (body: webhook.CallbackRequest): Promise<void> => {
50
- if (!body.events || body.events.length === 0) {
51
- return;
52
- }
53
-
54
- await handleLineWebhookEvents(body.events, {
55
- cfg,
56
- account,
57
- runtime,
58
- mediaMaxBytes,
59
- processMessage,
60
- replayCache,
61
- groupHistories,
62
- historyLimit: cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT,
63
- });
64
- };
65
-
66
- return {
67
- handleWebhook,
68
- account,
69
- };
70
- }