@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.
- package/dist/accounts-CD4A1FE7.js +105 -0
- package/dist/api.js +11 -0
- package/dist/basic-cards-BISytiSa.js +307 -0
- package/dist/card-command-dQBX3fVN.js +240 -0
- package/dist/channel-DV5h44-j.js +649 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.runtime-Cc-v3szZ.js +4 -0
- package/dist/contract-api.js +2 -0
- package/dist/index.js +45 -0
- package/dist/markdown-to-line-CC3BU6CC.js +810 -0
- package/dist/monitor-Ci8Hg8ay.js +1485 -0
- package/dist/monitor.runtime-t6-QvlDB.js +2 -0
- package/dist/outbound.runtime-D1CxEvcL.js +2 -0
- package/dist/probe-BPSs_A_8.js +30 -0
- package/dist/probe.runtime-7u2o9QN5.js +2 -0
- package/dist/reply-payload-transform-CDuBzoT4.js +855 -0
- package/dist/runtime-api.js +291 -0
- package/dist/schedule-cards-D-yZMHDE.js +359 -0
- package/dist/secret-contract-api.js +5 -0
- package/dist/setup-api.js +2 -0
- package/dist/setup-entry.js +11 -0
- package/dist/setup-surface-CHfQ6Z4i.js +282 -0
- package/package.json +19 -7
- package/api.js +0 -7
- package/channel-plugin-api.js +0 -7
- package/contract-api.js +0 -7
- package/index.js +0 -7
- package/runtime-api.js +0 -7
- package/secret-contract-api.js +0 -7
- package/setup-api.js +0 -7
- package/setup-entry.js +0 -7
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
import { i as resolveLineAccount, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-CD4A1FE7.js";
|
|
2
|
+
import { n as lineSetupAdapter, r as hasLineCredentials, t as lineSetupWizard } from "./setup-surface-CHfQ6Z4i.js";
|
|
3
|
+
import { c as resolveLineOutboundMedia, f as LineChannelConfigSchema, m as resolveExactLineGroupConfigKey, n as parseLineDirectives, o as createLineSendReceipt, s as buildLineQuickReplyFallbackText, t as hasLineDirectives, u as getLineRuntime } from "./reply-payload-transform-CDuBzoT4.js";
|
|
4
|
+
import { createChatChannelPlugin } from "klaw/plugin-sdk/channel-core";
|
|
5
|
+
import { createPairingPrefixStripper } from "klaw/plugin-sdk/channel-pairing";
|
|
6
|
+
import { createRestrictSendersChannelSecurity, resolveChannelGroupRequireMention } from "klaw/plugin-sdk/channel-policy";
|
|
7
|
+
import { createEmptyChannelDirectoryAdapter } from "klaw/plugin-sdk/directory-runtime";
|
|
8
|
+
import { createLazyRuntimeModule } from "klaw/plugin-sdk/lazy-runtime";
|
|
9
|
+
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1 } from "klaw/plugin-sdk/account-id";
|
|
10
|
+
import { clearAccountEntryFields } from "klaw/plugin-sdk/core";
|
|
11
|
+
import { describeWebhookAccountSnapshot } from "klaw/plugin-sdk/account-helpers";
|
|
12
|
+
import { createScopedChannelConfigAdapter } from "klaw/plugin-sdk/channel-config-helpers";
|
|
13
|
+
import { defineChannelMessageAdapter } from "klaw/plugin-sdk/channel-message";
|
|
14
|
+
import { createAttachedChannelResultAdapter, createEmptyChannelResult } from "klaw/plugin-sdk/channel-send-result";
|
|
15
|
+
import { resolveOutboundMediaUrls } from "klaw/plugin-sdk/reply-payload";
|
|
16
|
+
import { buildTokenChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState, createDependentCredentialStatusIssueCollector } from "klaw/plugin-sdk/status-helpers";
|
|
17
|
+
//#region extensions/line/src/bindings.ts
|
|
18
|
+
function normalizeLineConversationId(raw) {
|
|
19
|
+
const trimmed = raw?.trim() ?? "";
|
|
20
|
+
if (!trimmed) return null;
|
|
21
|
+
return (trimmed.match(/^line:(?:(?:user|group|room):)?(.+)$/i)?.[1] ?? trimmed).trim() || null;
|
|
22
|
+
}
|
|
23
|
+
function resolveLineCommandConversation(params) {
|
|
24
|
+
const conversationId = normalizeLineConversationId(params.originatingTo) ?? normalizeLineConversationId(params.commandTo) ?? normalizeLineConversationId(params.fallbackTo);
|
|
25
|
+
return conversationId ? { conversationId } : null;
|
|
26
|
+
}
|
|
27
|
+
function resolveLineInboundConversation(params) {
|
|
28
|
+
const conversationId = normalizeLineConversationId(params.conversationId) ?? normalizeLineConversationId(params.to);
|
|
29
|
+
return conversationId ? { conversationId } : null;
|
|
30
|
+
}
|
|
31
|
+
const lineBindingsAdapter = {
|
|
32
|
+
compileConfiguredBinding: ({ conversationId }) => {
|
|
33
|
+
const normalized = normalizeLineConversationId(conversationId);
|
|
34
|
+
return normalized ? { conversationId: normalized } : null;
|
|
35
|
+
},
|
|
36
|
+
matchInboundConversation: ({ compiledBinding, conversationId }) => {
|
|
37
|
+
const normalizedIncoming = normalizeLineConversationId(conversationId);
|
|
38
|
+
if (!normalizedIncoming || compiledBinding.conversationId !== normalizedIncoming) return null;
|
|
39
|
+
return {
|
|
40
|
+
conversationId: normalizedIncoming,
|
|
41
|
+
matchPriority: 2
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
resolveCommandConversation: ({ originatingTo, commandTo, fallbackTo }) => resolveLineCommandConversation({
|
|
45
|
+
originatingTo,
|
|
46
|
+
commandTo,
|
|
47
|
+
fallbackTo
|
|
48
|
+
}),
|
|
49
|
+
resolveInboundConversation: ({ to, conversationId }) => resolveLineInboundConversation({
|
|
50
|
+
to,
|
|
51
|
+
conversationId
|
|
52
|
+
})
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region extensions/line/src/config-adapter.ts
|
|
56
|
+
function normalizeLineAllowFrom(entry) {
|
|
57
|
+
return entry.replace(/^line:(?:user:)?/i, "");
|
|
58
|
+
}
|
|
59
|
+
const lineChannelPluginCommon = {
|
|
60
|
+
meta: {
|
|
61
|
+
id: "line",
|
|
62
|
+
label: "LINE",
|
|
63
|
+
selectionLabel: "LINE (Messaging API)",
|
|
64
|
+
detailLabel: "LINE Bot",
|
|
65
|
+
docsPath: "/channels/line",
|
|
66
|
+
docsLabel: "line",
|
|
67
|
+
blurb: "LINE Messaging API bot for Japan/Taiwan/Thailand markets.",
|
|
68
|
+
systemImage: "message.fill",
|
|
69
|
+
quickstartAllowFrom: true
|
|
70
|
+
},
|
|
71
|
+
capabilities: {
|
|
72
|
+
chatTypes: ["direct", "group"],
|
|
73
|
+
reactions: false,
|
|
74
|
+
threads: false,
|
|
75
|
+
media: true,
|
|
76
|
+
nativeCommands: false,
|
|
77
|
+
blockStreaming: true
|
|
78
|
+
},
|
|
79
|
+
reload: { configPrefixes: ["channels.line"] },
|
|
80
|
+
configSchema: LineChannelConfigSchema,
|
|
81
|
+
config: {
|
|
82
|
+
...createScopedChannelConfigAdapter({
|
|
83
|
+
sectionKey: "line",
|
|
84
|
+
listAccountIds: listLineAccountIds,
|
|
85
|
+
resolveAccount: (cfg, accountId) => resolveLineAccount({
|
|
86
|
+
cfg,
|
|
87
|
+
accountId: accountId ?? void 0
|
|
88
|
+
}),
|
|
89
|
+
defaultAccountId: resolveDefaultLineAccountId,
|
|
90
|
+
clearBaseFields: [
|
|
91
|
+
"channelSecret",
|
|
92
|
+
"tokenFile",
|
|
93
|
+
"secretFile"
|
|
94
|
+
],
|
|
95
|
+
resolveAllowFrom: (account) => account.config.allowFrom,
|
|
96
|
+
formatAllowFrom: (allowFrom) => allowFrom.map((entry) => String(entry).trim()).filter(Boolean).map(normalizeLineAllowFrom)
|
|
97
|
+
}),
|
|
98
|
+
isConfigured: (account) => hasLineCredentials(account),
|
|
99
|
+
describeAccount: (account) => describeWebhookAccountSnapshot({
|
|
100
|
+
account,
|
|
101
|
+
configured: hasLineCredentials(account),
|
|
102
|
+
extra: { tokenSource: account.tokenSource ?? void 0 }
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region extensions/line/src/gateway.ts
|
|
108
|
+
const loadLineProbeRuntime$1 = createLazyRuntimeModule(() => import("./probe.runtime-7u2o9QN5.js"));
|
|
109
|
+
const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-t6-QvlDB.js"));
|
|
110
|
+
const lineGatewayAdapter = {
|
|
111
|
+
startAccount: async (ctx) => {
|
|
112
|
+
const account = ctx.account;
|
|
113
|
+
const token = account.channelAccessToken.trim();
|
|
114
|
+
const secret = account.channelSecret.trim();
|
|
115
|
+
if (!token) throw new Error(`LINE webhook mode requires a non-empty channel access token for account "${account.accountId}".`);
|
|
116
|
+
if (!secret) throw new Error(`LINE webhook mode requires a non-empty channel secret for account "${account.accountId}".`);
|
|
117
|
+
let lineBotLabel = "";
|
|
118
|
+
try {
|
|
119
|
+
const probe = await (await loadLineProbeRuntime$1()).probeLineBot(token, 2500);
|
|
120
|
+
const displayName = probe.ok ? probe.bot?.displayName?.trim() : null;
|
|
121
|
+
if (displayName) lineBotLabel = ` (${displayName})`;
|
|
122
|
+
} catch (err) {
|
|
123
|
+
if (getLineRuntime().logging.shouldLogVerbose()) ctx.log?.debug?.(`[${account.accountId}] bot probe failed: ${String(err)}`);
|
|
124
|
+
}
|
|
125
|
+
ctx.log?.info(`[${account.accountId}] starting LINE provider${lineBotLabel}`);
|
|
126
|
+
return await (getLineRuntime().channel.line?.monitorLineProvider ?? (await loadLineMonitorRuntime()).monitorLineProvider)({
|
|
127
|
+
channelAccessToken: token,
|
|
128
|
+
channelSecret: secret,
|
|
129
|
+
accountId: account.accountId,
|
|
130
|
+
config: ctx.cfg,
|
|
131
|
+
runtime: ctx.runtime,
|
|
132
|
+
abortSignal: ctx.abortSignal,
|
|
133
|
+
webhookPath: account.config.webhookPath
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
logoutAccount: async ({ accountId, cfg }) => {
|
|
137
|
+
const envToken = process.env.LINE_CHANNEL_ACCESS_TOKEN?.trim() ?? "";
|
|
138
|
+
const nextCfg = { ...cfg };
|
|
139
|
+
const nextLine = { ...cfg.channels?.line ?? {} };
|
|
140
|
+
let cleared = false;
|
|
141
|
+
let changed = false;
|
|
142
|
+
if (accountId === DEFAULT_ACCOUNT_ID$1) {
|
|
143
|
+
if (nextLine.channelAccessToken || nextLine.channelSecret || nextLine.tokenFile || nextLine.secretFile) {
|
|
144
|
+
delete nextLine.channelAccessToken;
|
|
145
|
+
delete nextLine.channelSecret;
|
|
146
|
+
delete nextLine.tokenFile;
|
|
147
|
+
delete nextLine.secretFile;
|
|
148
|
+
cleared = true;
|
|
149
|
+
changed = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const accountCleanup = clearAccountEntryFields({
|
|
153
|
+
accounts: nextLine.accounts,
|
|
154
|
+
accountId,
|
|
155
|
+
fields: [
|
|
156
|
+
"channelAccessToken",
|
|
157
|
+
"channelSecret",
|
|
158
|
+
"tokenFile",
|
|
159
|
+
"secretFile"
|
|
160
|
+
],
|
|
161
|
+
markClearedOnFieldPresence: true
|
|
162
|
+
});
|
|
163
|
+
if (accountCleanup.changed) {
|
|
164
|
+
changed = true;
|
|
165
|
+
if (accountCleanup.cleared) cleared = true;
|
|
166
|
+
if (accountCleanup.nextAccounts) nextLine.accounts = accountCleanup.nextAccounts;
|
|
167
|
+
else delete nextLine.accounts;
|
|
168
|
+
}
|
|
169
|
+
if (changed) {
|
|
170
|
+
if (Object.keys(nextLine).length > 0) nextCfg.channels = {
|
|
171
|
+
...nextCfg.channels,
|
|
172
|
+
line: nextLine
|
|
173
|
+
};
|
|
174
|
+
else {
|
|
175
|
+
const nextChannels = { ...nextCfg.channels };
|
|
176
|
+
delete nextChannels.line;
|
|
177
|
+
if (Object.keys(nextChannels).length > 0) nextCfg.channels = nextChannels;
|
|
178
|
+
else delete nextCfg.channels;
|
|
179
|
+
}
|
|
180
|
+
await getLineRuntime().config.replaceConfigFile({
|
|
181
|
+
nextConfig: nextCfg,
|
|
182
|
+
afterWrite: { mode: "auto" }
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
const loggedOut = resolveLineAccount({
|
|
186
|
+
cfg: changed ? nextCfg : cfg,
|
|
187
|
+
accountId
|
|
188
|
+
}).tokenSource === "none";
|
|
189
|
+
return {
|
|
190
|
+
cleared,
|
|
191
|
+
envToken: Boolean(envToken),
|
|
192
|
+
loggedOut
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region extensions/line/src/group-policy.ts
|
|
198
|
+
function resolveLineGroupRequireMention(params) {
|
|
199
|
+
const exactGroupId = resolveExactLineGroupConfigKey({
|
|
200
|
+
cfg: params.cfg,
|
|
201
|
+
accountId: params.accountId,
|
|
202
|
+
groupId: params.groupId
|
|
203
|
+
});
|
|
204
|
+
return resolveChannelGroupRequireMention({
|
|
205
|
+
cfg: params.cfg,
|
|
206
|
+
channel: "line",
|
|
207
|
+
groupId: exactGroupId ?? params.groupId,
|
|
208
|
+
accountId: params.accountId
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
//#endregion
|
|
212
|
+
//#region extensions/line/src/outbound.ts
|
|
213
|
+
const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-D1CxEvcL.js"));
|
|
214
|
+
function isLineUserTarget(target) {
|
|
215
|
+
const normalized = target.trim().replace(/^line:(group|room|user):/i, "").replace(/^line:/i, "");
|
|
216
|
+
return /^U/i.test(normalized);
|
|
217
|
+
}
|
|
218
|
+
function hasLineSpecificMediaOptions(lineData) {
|
|
219
|
+
return Boolean(lineData.mediaKind ?? lineData.previewImageUrl?.trim() ?? (typeof lineData.durationMs === "number" ? lineData.durationMs : void 0) ?? lineData.trackingId?.trim());
|
|
220
|
+
}
|
|
221
|
+
function buildLineMediaMessageObject(resolved, opts) {
|
|
222
|
+
switch (resolved.mediaKind) {
|
|
223
|
+
case "video": {
|
|
224
|
+
const previewImageUrl = resolved.previewImageUrl?.trim();
|
|
225
|
+
if (!previewImageUrl) throw new Error("LINE video messages require previewImageUrl to reference an image URL");
|
|
226
|
+
return {
|
|
227
|
+
type: "video",
|
|
228
|
+
originalContentUrl: resolved.mediaUrl,
|
|
229
|
+
previewImageUrl,
|
|
230
|
+
...opts?.allowTrackingId && resolved.trackingId ? { trackingId: resolved.trackingId } : {}
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
case "audio": return {
|
|
234
|
+
type: "audio",
|
|
235
|
+
originalContentUrl: resolved.mediaUrl,
|
|
236
|
+
duration: resolved.durationMs ?? 6e4
|
|
237
|
+
};
|
|
238
|
+
default: return {
|
|
239
|
+
type: "image",
|
|
240
|
+
originalContentUrl: resolved.mediaUrl,
|
|
241
|
+
previewImageUrl: resolved.previewImageUrl ?? resolved.mediaUrl
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const lineOutboundAdapter = {
|
|
246
|
+
deliveryMode: "direct",
|
|
247
|
+
chunker: (text, limit) => getLineRuntime().channel.text.chunkMarkdownText(text, limit),
|
|
248
|
+
textChunkLimit: 5e3,
|
|
249
|
+
sendPayload: async ({ to, payload, accountId, cfg }) => {
|
|
250
|
+
const runtime = getLineRuntime();
|
|
251
|
+
const outboundRuntime = await loadLineOutboundRuntime();
|
|
252
|
+
const lineData = payload.channelData?.line ?? {};
|
|
253
|
+
const lineRuntime = runtime.channel.line;
|
|
254
|
+
const sendText = lineRuntime?.pushMessageLine ?? outboundRuntime.pushMessageLine;
|
|
255
|
+
const sendBatch = lineRuntime?.pushMessagesLine ?? outboundRuntime.pushMessagesLine;
|
|
256
|
+
const sendFlex = lineRuntime?.pushFlexMessage ?? outboundRuntime.pushFlexMessage;
|
|
257
|
+
const sendTemplate = lineRuntime?.pushTemplateMessage ?? outboundRuntime.pushTemplateMessage;
|
|
258
|
+
const sendLocation = lineRuntime?.pushLocationMessage ?? outboundRuntime.pushLocationMessage;
|
|
259
|
+
const sendQuickReplies = lineRuntime?.pushTextMessageWithQuickReplies ?? outboundRuntime.pushTextMessageWithQuickReplies;
|
|
260
|
+
const buildTemplate = lineRuntime?.buildTemplateMessageFromPayload ?? outboundRuntime.buildTemplateMessageFromPayload;
|
|
261
|
+
let lastResult = null;
|
|
262
|
+
const quickReplies = lineData.quickReplies ?? [];
|
|
263
|
+
const hasQuickReplies = quickReplies.length > 0;
|
|
264
|
+
const quickReply = hasQuickReplies ? (lineRuntime?.createQuickReplyItems ?? outboundRuntime.createQuickReplyItems)(quickReplies) : void 0;
|
|
265
|
+
const sendMessageBatch = async (messages) => {
|
|
266
|
+
if (messages.length === 0) return;
|
|
267
|
+
for (let i = 0; i < messages.length; i += 5) lastResult = await sendBatch(to, messages.slice(i, i + 5), {
|
|
268
|
+
verbose: false,
|
|
269
|
+
cfg,
|
|
270
|
+
accountId: accountId ?? void 0
|
|
271
|
+
});
|
|
272
|
+
};
|
|
273
|
+
const processed = payload.text ? outboundRuntime.processLineMessage(payload.text) : {
|
|
274
|
+
text: "",
|
|
275
|
+
flexMessages: []
|
|
276
|
+
};
|
|
277
|
+
const chunkLimit = runtime.channel.text.resolveTextChunkLimit?.(cfg, "line", accountId ?? void 0, { fallbackLimit: 5e3 }) ?? 5e3;
|
|
278
|
+
const chunks = processed.text ? runtime.channel.text.chunkMarkdownText(processed.text, chunkLimit) : [];
|
|
279
|
+
const mediaUrls = resolveOutboundMediaUrls(payload);
|
|
280
|
+
const useLineSpecificMedia = hasLineSpecificMediaOptions(lineData);
|
|
281
|
+
const shouldSendQuickRepliesInline = chunks.length === 0 && hasQuickReplies;
|
|
282
|
+
const sendMediaMessages = async () => {
|
|
283
|
+
for (const url of mediaUrls) {
|
|
284
|
+
const trimmed = url?.trim();
|
|
285
|
+
if (!trimmed) continue;
|
|
286
|
+
if (!useLineSpecificMedia) {
|
|
287
|
+
lastResult = await (lineRuntime?.sendMessageLine ?? outboundRuntime.sendMessageLine)(to, "", {
|
|
288
|
+
verbose: false,
|
|
289
|
+
mediaUrl: trimmed,
|
|
290
|
+
cfg,
|
|
291
|
+
accountId: accountId ?? void 0
|
|
292
|
+
});
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const resolved = await resolveLineOutboundMedia(trimmed, {
|
|
296
|
+
mediaKind: lineData.mediaKind,
|
|
297
|
+
previewImageUrl: lineData.previewImageUrl,
|
|
298
|
+
durationMs: lineData.durationMs,
|
|
299
|
+
trackingId: lineData.trackingId
|
|
300
|
+
});
|
|
301
|
+
lastResult = await (lineRuntime?.sendMessageLine ?? outboundRuntime.sendMessageLine)(to, "", {
|
|
302
|
+
verbose: false,
|
|
303
|
+
mediaUrl: resolved.mediaUrl,
|
|
304
|
+
mediaKind: resolved.mediaKind,
|
|
305
|
+
previewImageUrl: resolved.previewImageUrl,
|
|
306
|
+
durationMs: resolved.durationMs,
|
|
307
|
+
trackingId: resolved.trackingId,
|
|
308
|
+
cfg,
|
|
309
|
+
accountId: accountId ?? void 0
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
if (!shouldSendQuickRepliesInline) {
|
|
314
|
+
if (lineData.flexMessage) {
|
|
315
|
+
const flexContents = lineData.flexMessage.contents;
|
|
316
|
+
lastResult = await sendFlex(to, lineData.flexMessage.altText, flexContents, {
|
|
317
|
+
verbose: false,
|
|
318
|
+
cfg,
|
|
319
|
+
accountId: accountId ?? void 0
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
if (lineData.templateMessage) {
|
|
323
|
+
const template = buildTemplate(lineData.templateMessage);
|
|
324
|
+
if (template) lastResult = await sendTemplate(to, template, {
|
|
325
|
+
verbose: false,
|
|
326
|
+
cfg,
|
|
327
|
+
accountId: accountId ?? void 0
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
if (lineData.location) lastResult = await sendLocation(to, lineData.location, {
|
|
331
|
+
verbose: false,
|
|
332
|
+
cfg,
|
|
333
|
+
accountId: accountId ?? void 0
|
|
334
|
+
});
|
|
335
|
+
for (const flexMsg of processed.flexMessages) {
|
|
336
|
+
const flexContents = flexMsg.contents;
|
|
337
|
+
lastResult = await sendFlex(to, flexMsg.altText, flexContents, {
|
|
338
|
+
verbose: false,
|
|
339
|
+
cfg,
|
|
340
|
+
accountId: accountId ?? void 0
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const sendMediaAfterText = !(hasQuickReplies && chunks.length > 0);
|
|
345
|
+
if (mediaUrls.length > 0 && !shouldSendQuickRepliesInline && !sendMediaAfterText) await sendMediaMessages();
|
|
346
|
+
if (chunks.length > 0) for (let i = 0; i < chunks.length; i += 1) if (i === chunks.length - 1 && hasQuickReplies) lastResult = await sendQuickReplies(to, chunks[i], quickReplies, {
|
|
347
|
+
verbose: false,
|
|
348
|
+
cfg,
|
|
349
|
+
accountId: accountId ?? void 0
|
|
350
|
+
});
|
|
351
|
+
else lastResult = await sendText(to, chunks[i], {
|
|
352
|
+
verbose: false,
|
|
353
|
+
cfg,
|
|
354
|
+
accountId: accountId ?? void 0
|
|
355
|
+
});
|
|
356
|
+
else if (shouldSendQuickRepliesInline) {
|
|
357
|
+
const quickReplyMessages = [];
|
|
358
|
+
if (lineData.flexMessage) quickReplyMessages.push({
|
|
359
|
+
type: "flex",
|
|
360
|
+
altText: lineData.flexMessage.altText.slice(0, 400),
|
|
361
|
+
contents: lineData.flexMessage.contents
|
|
362
|
+
});
|
|
363
|
+
if (lineData.templateMessage) {
|
|
364
|
+
const template = buildTemplate(lineData.templateMessage);
|
|
365
|
+
if (template) quickReplyMessages.push(template);
|
|
366
|
+
}
|
|
367
|
+
if (lineData.location) quickReplyMessages.push({
|
|
368
|
+
type: "location",
|
|
369
|
+
title: lineData.location.title.slice(0, 100),
|
|
370
|
+
address: lineData.location.address.slice(0, 100),
|
|
371
|
+
latitude: lineData.location.latitude,
|
|
372
|
+
longitude: lineData.location.longitude
|
|
373
|
+
});
|
|
374
|
+
for (const flexMsg of processed.flexMessages) quickReplyMessages.push({
|
|
375
|
+
type: "flex",
|
|
376
|
+
altText: flexMsg.altText.slice(0, 400),
|
|
377
|
+
contents: flexMsg.contents
|
|
378
|
+
});
|
|
379
|
+
for (const url of mediaUrls) {
|
|
380
|
+
const trimmed = url?.trim();
|
|
381
|
+
if (!trimmed) continue;
|
|
382
|
+
if (!useLineSpecificMedia) {
|
|
383
|
+
quickReplyMessages.push({
|
|
384
|
+
type: "image",
|
|
385
|
+
originalContentUrl: trimmed,
|
|
386
|
+
previewImageUrl: trimmed
|
|
387
|
+
});
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
const resolved = await resolveLineOutboundMedia(trimmed, {
|
|
391
|
+
mediaKind: lineData.mediaKind,
|
|
392
|
+
previewImageUrl: lineData.previewImageUrl,
|
|
393
|
+
durationMs: lineData.durationMs,
|
|
394
|
+
trackingId: lineData.trackingId
|
|
395
|
+
});
|
|
396
|
+
quickReplyMessages.push(buildLineMediaMessageObject(resolved, { allowTrackingId: isLineUserTarget(to) }));
|
|
397
|
+
}
|
|
398
|
+
if (quickReplyMessages.length > 0 && quickReply) {
|
|
399
|
+
const lastIndex = quickReplyMessages.length - 1;
|
|
400
|
+
quickReplyMessages[lastIndex] = {
|
|
401
|
+
...quickReplyMessages[lastIndex],
|
|
402
|
+
quickReply
|
|
403
|
+
};
|
|
404
|
+
await sendMessageBatch(quickReplyMessages);
|
|
405
|
+
} else if (quickReply) lastResult = await sendQuickReplies(to, buildLineQuickReplyFallbackText(quickReplies), quickReplies, {
|
|
406
|
+
verbose: false,
|
|
407
|
+
cfg,
|
|
408
|
+
accountId: accountId ?? void 0
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
if (mediaUrls.length > 0 && !shouldSendQuickRepliesInline && sendMediaAfterText) await sendMediaMessages();
|
|
412
|
+
if (lastResult) return createEmptyChannelResult("line", { ...lastResult });
|
|
413
|
+
return createEmptyChannelResult("line", {
|
|
414
|
+
messageId: "empty",
|
|
415
|
+
chatId: to
|
|
416
|
+
});
|
|
417
|
+
},
|
|
418
|
+
...createAttachedChannelResultAdapter({
|
|
419
|
+
channel: "line",
|
|
420
|
+
sendText: async ({ cfg, to, text, accountId }) => {
|
|
421
|
+
const outboundRuntime = await loadLineOutboundRuntime();
|
|
422
|
+
const sendText = outboundRuntime.pushMessageLine;
|
|
423
|
+
const sendFlex = outboundRuntime.pushFlexMessage;
|
|
424
|
+
const processed = outboundRuntime.processLineMessage(text);
|
|
425
|
+
let result;
|
|
426
|
+
if (processed.text.trim()) result = await sendText(to, processed.text, {
|
|
427
|
+
verbose: false,
|
|
428
|
+
cfg,
|
|
429
|
+
accountId: accountId ?? void 0
|
|
430
|
+
});
|
|
431
|
+
else result = {
|
|
432
|
+
messageId: "processed",
|
|
433
|
+
chatId: to,
|
|
434
|
+
receipt: createLineSendReceipt({
|
|
435
|
+
messageId: "processed",
|
|
436
|
+
chatId: to,
|
|
437
|
+
kind: "card"
|
|
438
|
+
})
|
|
439
|
+
};
|
|
440
|
+
for (const flexMsg of processed.flexMessages) {
|
|
441
|
+
const flexContents = flexMsg.contents;
|
|
442
|
+
await sendFlex(to, flexMsg.altText, flexContents, {
|
|
443
|
+
verbose: false,
|
|
444
|
+
cfg,
|
|
445
|
+
accountId: accountId ?? void 0
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
return result;
|
|
449
|
+
},
|
|
450
|
+
sendMedia: async ({ cfg, to, text, mediaUrl, accountId }) => await (await loadLineOutboundRuntime()).sendMessageLine(to, text, {
|
|
451
|
+
verbose: false,
|
|
452
|
+
mediaUrl,
|
|
453
|
+
cfg,
|
|
454
|
+
accountId: accountId ?? void 0
|
|
455
|
+
})
|
|
456
|
+
})
|
|
457
|
+
};
|
|
458
|
+
function toLineMessageSendResult(result, kind) {
|
|
459
|
+
const source = result;
|
|
460
|
+
const receipt = result.receipt ?? (result.messageId ? createLineSendReceipt({
|
|
461
|
+
messageId: result.messageId,
|
|
462
|
+
chatId: source.chatId ?? "",
|
|
463
|
+
kind
|
|
464
|
+
}) : void 0);
|
|
465
|
+
if (!receipt) throw new Error("LINE message adapter send did not return a receipt");
|
|
466
|
+
return {
|
|
467
|
+
messageId: result.messageId || receipt.primaryPlatformMessageId,
|
|
468
|
+
receipt
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
const lineMessageAdapter = defineChannelMessageAdapter({
|
|
472
|
+
id: "line",
|
|
473
|
+
durableFinal: { capabilities: {
|
|
474
|
+
text: true,
|
|
475
|
+
media: true,
|
|
476
|
+
messageSendingHooks: true
|
|
477
|
+
} },
|
|
478
|
+
send: {
|
|
479
|
+
text: async ({ cfg, to, text, accountId }) => {
|
|
480
|
+
return toLineMessageSendResult(await lineOutboundAdapter.sendPayload({
|
|
481
|
+
cfg,
|
|
482
|
+
to,
|
|
483
|
+
text,
|
|
484
|
+
accountId,
|
|
485
|
+
payload: { text }
|
|
486
|
+
}), "text");
|
|
487
|
+
},
|
|
488
|
+
media: async ({ cfg, to, text, mediaUrl, accountId }) => {
|
|
489
|
+
return toLineMessageSendResult(await lineOutboundAdapter.sendPayload({
|
|
490
|
+
cfg,
|
|
491
|
+
to,
|
|
492
|
+
text,
|
|
493
|
+
mediaUrl,
|
|
494
|
+
accountId,
|
|
495
|
+
payload: {
|
|
496
|
+
text,
|
|
497
|
+
mediaUrl
|
|
498
|
+
}
|
|
499
|
+
}), "media");
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
receive: {
|
|
503
|
+
defaultAckPolicy: "after_receive_record",
|
|
504
|
+
supportedAckPolicies: ["after_receive_record"]
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
//#endregion
|
|
508
|
+
//#region extensions/line/src/status.ts
|
|
509
|
+
const loadLineProbeRuntime = createLazyRuntimeModule(() => import("./probe.runtime-7u2o9QN5.js"));
|
|
510
|
+
const collectLineStatusIssues = createDependentCredentialStatusIssueCollector({
|
|
511
|
+
channel: "line",
|
|
512
|
+
dependencySourceKey: "tokenSource",
|
|
513
|
+
missingPrimaryMessage: "LINE channel access token not configured",
|
|
514
|
+
missingDependentMessage: "LINE channel secret not configured"
|
|
515
|
+
});
|
|
516
|
+
const lineStatusAdapter = createComputedAccountStatusAdapter({
|
|
517
|
+
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID$1),
|
|
518
|
+
collectStatusIssues: collectLineStatusIssues,
|
|
519
|
+
buildChannelSummary: ({ snapshot }) => buildTokenChannelStatusSummary(snapshot),
|
|
520
|
+
probeAccount: async ({ account, timeoutMs }) => await (await loadLineProbeRuntime()).probeLineBot(account.channelAccessToken, timeoutMs),
|
|
521
|
+
resolveAccountSnapshot: ({ account }) => ({
|
|
522
|
+
accountId: account.accountId,
|
|
523
|
+
name: account.name,
|
|
524
|
+
enabled: account.enabled,
|
|
525
|
+
configured: hasLineCredentials(account),
|
|
526
|
+
extra: {
|
|
527
|
+
tokenSource: account.tokenSource,
|
|
528
|
+
mode: "webhook"
|
|
529
|
+
}
|
|
530
|
+
})
|
|
531
|
+
});
|
|
532
|
+
//#endregion
|
|
533
|
+
//#region extensions/line/src/channel.ts
|
|
534
|
+
const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-Cc-v3szZ.js"));
|
|
535
|
+
const lineSecurityAdapter = createRestrictSendersChannelSecurity({
|
|
536
|
+
channelKey: "line",
|
|
537
|
+
resolveDmPolicy: (account) => account.config.dmPolicy,
|
|
538
|
+
resolveDmAllowFrom: (account) => account.config.allowFrom,
|
|
539
|
+
resolveGroupPolicy: (account) => account.config.groupPolicy,
|
|
540
|
+
surface: "LINE groups",
|
|
541
|
+
openScope: "any member in groups",
|
|
542
|
+
groupPolicyPath: "channels.line.groupPolicy",
|
|
543
|
+
groupAllowFromPath: "channels.line.groupAllowFrom",
|
|
544
|
+
mentionGated: false,
|
|
545
|
+
policyPathSuffix: "dmPolicy",
|
|
546
|
+
approveHint: "klaw pairing approve line <code>",
|
|
547
|
+
normalizeDmEntry: (raw) => raw.replace(/^line:(?:user:)?/i, "")
|
|
548
|
+
});
|
|
549
|
+
const linePlugin = createChatChannelPlugin({
|
|
550
|
+
base: {
|
|
551
|
+
id: "line",
|
|
552
|
+
...lineChannelPluginCommon,
|
|
553
|
+
setupWizard: lineSetupWizard,
|
|
554
|
+
groups: { resolveRequireMention: resolveLineGroupRequireMention },
|
|
555
|
+
messaging: {
|
|
556
|
+
targetPrefixes: ["line"],
|
|
557
|
+
normalizeTarget: (target) => {
|
|
558
|
+
const trimmed = target.trim();
|
|
559
|
+
if (!trimmed) return;
|
|
560
|
+
return trimmed.replace(/^line:(group|room|user):/i, "").replace(/^line:/i, "");
|
|
561
|
+
},
|
|
562
|
+
resolveInboundConversation: lineBindingsAdapter.resolveInboundConversation,
|
|
563
|
+
transformReplyPayload: ({ payload }) => {
|
|
564
|
+
if (!payload.text || !hasLineDirectives(payload.text)) return payload;
|
|
565
|
+
return parseLineDirectives(payload);
|
|
566
|
+
},
|
|
567
|
+
targetResolver: {
|
|
568
|
+
looksLikeId: (id) => {
|
|
569
|
+
const trimmed = id?.trim();
|
|
570
|
+
if (!trimmed) return false;
|
|
571
|
+
return /^[UCR][a-f0-9]{32}$/i.test(trimmed) || /^line:/i.test(trimmed);
|
|
572
|
+
},
|
|
573
|
+
hint: "<userId|groupId|roomId>"
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
directory: createEmptyChannelDirectoryAdapter(),
|
|
577
|
+
setup: lineSetupAdapter,
|
|
578
|
+
status: lineStatusAdapter,
|
|
579
|
+
gateway: lineGatewayAdapter,
|
|
580
|
+
message: lineMessageAdapter,
|
|
581
|
+
bindings: lineBindingsAdapter,
|
|
582
|
+
conversationBindings: { defaultTopLevelPlacement: "current" },
|
|
583
|
+
agentPrompt: { messageToolHints: () => [
|
|
584
|
+
"",
|
|
585
|
+
"### LINE Rich Messages",
|
|
586
|
+
"LINE supports rich visual messages. Use these directives in your reply when appropriate:",
|
|
587
|
+
"",
|
|
588
|
+
"**Quick Replies** (bottom button suggestions):",
|
|
589
|
+
" [[quick_replies: Option 1, Option 2, Option 3]]",
|
|
590
|
+
"",
|
|
591
|
+
"**Location** (map pin):",
|
|
592
|
+
" [[location: Place Name | Address | latitude | longitude]]",
|
|
593
|
+
"",
|
|
594
|
+
"**Confirm Dialog** (yes/no prompt):",
|
|
595
|
+
" [[confirm: Question text? | Yes Label | No Label]]",
|
|
596
|
+
"",
|
|
597
|
+
"**Button Menu** (title + text + buttons):",
|
|
598
|
+
" [[buttons: Title | Description | Btn1:action1, Btn2:https://url.com]]",
|
|
599
|
+
"",
|
|
600
|
+
"**Media Player Card** (music status):",
|
|
601
|
+
" [[media_player: Song Title | Artist Name | Source | https://albumart.url | playing]]",
|
|
602
|
+
" - Status: 'playing' or 'paused' (optional)",
|
|
603
|
+
"",
|
|
604
|
+
"**Event Card** (calendar events, meetings):",
|
|
605
|
+
" [[event: Event Title | Date | Time | Location | Description]]",
|
|
606
|
+
" - Time, Location, Description are optional",
|
|
607
|
+
"",
|
|
608
|
+
"**Agenda Card** (multiple events/schedule):",
|
|
609
|
+
" [[agenda: Schedule Title | Event1:9:00 AM, Event2:12:00 PM, Event3:3:00 PM]]",
|
|
610
|
+
"",
|
|
611
|
+
"**Device Control Card** (smart devices, TVs, etc.):",
|
|
612
|
+
" [[device: Device Name | Device Type | Status | Control1:data1, Control2:data2]]",
|
|
613
|
+
"",
|
|
614
|
+
"**Apple TV Remote** (full D-pad + transport):",
|
|
615
|
+
" [[appletv_remote: Apple TV | Playing]]",
|
|
616
|
+
"",
|
|
617
|
+
"**Auto-converted**: Markdown tables become Flex cards, code blocks become styled cards.",
|
|
618
|
+
"",
|
|
619
|
+
"When to use rich messages:",
|
|
620
|
+
"- Use [[quick_replies:...]] when offering 2-4 clear options",
|
|
621
|
+
"- Use [[confirm:...]] for yes/no decisions",
|
|
622
|
+
"- Use [[buttons:...]] for menus with actions/links",
|
|
623
|
+
"- Use [[location:...]] when sharing a place",
|
|
624
|
+
"- Use [[media_player:...]] when showing what's playing",
|
|
625
|
+
"- Use [[event:...]] for calendar event details",
|
|
626
|
+
"- Use [[agenda:...]] for a day's schedule or event list",
|
|
627
|
+
"- Use [[device:...]] for smart device status/controls",
|
|
628
|
+
"- Tables/code in your response auto-convert to visual cards"
|
|
629
|
+
] }
|
|
630
|
+
},
|
|
631
|
+
pairing: { text: {
|
|
632
|
+
idLabel: "lineUserId",
|
|
633
|
+
message: "Klaw: your access has been approved.",
|
|
634
|
+
normalizeAllowEntry: createPairingPrefixStripper(/^line:(?:user:)?/i),
|
|
635
|
+
notify: async ({ cfg, id, message }) => {
|
|
636
|
+
const account = (getLineRuntime().channel.line?.resolveLineAccount ?? resolveLineAccount)({ cfg });
|
|
637
|
+
if (!account.channelAccessToken) throw new Error("LINE channel access token not configured");
|
|
638
|
+
await (getLineRuntime().channel.line?.pushMessageLine ?? (await loadLineChannelRuntime()).pushMessageLine)(id, message, {
|
|
639
|
+
cfg,
|
|
640
|
+
accountId: account.accountId,
|
|
641
|
+
channelAccessToken: account.channelAccessToken
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
} },
|
|
645
|
+
security: lineSecurityAdapter,
|
|
646
|
+
outbound: lineOutboundAdapter
|
|
647
|
+
});
|
|
648
|
+
//#endregion
|
|
649
|
+
export { lineChannelPluginCommon as n, linePlugin as t };
|