@openclaw/line 2026.9.1-beta.1 → 2026.9.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/account-helpers-BSUyZwLm.js +12 -0
- package/dist/{accounts-BqEVsDoB.js → accounts-BBEFMYbY.js} +2 -1
- package/dist/api.js +2 -2
- package/dist/card-command-pwvE48Wb.js +252 -0
- package/dist/{channel-CsQLcrsA.js → channel-CF2AeULJ.js} +111 -380
- package/dist/channel-plugin-api.js +1 -1
- package/dist/channel.runtime-BF4Wk2dM.js +4 -0
- package/dist/contract-api.js +1 -1
- package/dist/doctor-contract-api.js +141 -0
- package/dist/index.js +5 -17
- package/dist/{markdown-to-line-BmZ4hhfC.js → markdown-to-line-B-sj-7II.js} +22 -23
- package/dist/{monitor-Bd9XDV1i.js → monitor-D0fHFXCi.js} +323 -234
- package/dist/monitor.runtime-Mgecav_D.js +2 -0
- package/dist/outbound.runtime-CGqyJ-Ug.js +3 -0
- package/dist/probe.runtime-CbtMgU4s.js +2 -0
- package/dist/rich-messages-DY1AGKAJ.js +742 -0
- package/dist/runtime-api.js +14 -15
- package/dist/{send-Dce0m539.js → send-CQQxpxPp.js} +142 -112
- package/dist/{send-receipt-CCPTalf9.js → send-retry-ChpfJ-LM.js} +326 -86
- package/dist/setup-api.js +1 -1
- package/dist/{setup-surface-W0Dbgu58.js → setup-surface-CDlgkTKi.js} +47 -39
- package/dist/webhook-spool-contract-1rY8OJw8.js +59 -0
- package/node_modules/typebox/build/format/email.mjs +1 -1
- package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
- package/node_modules/typebox/package.json +1 -1
- package/openclaw.plugin.json +16 -2
- package/package.json +5 -5
- package/dist/card-command-CmU47gD9.js +0 -230
- package/dist/channel.runtime-B83x7k_i.js +0 -4
- package/dist/media-control-cards-CksNB8Wk.js +0 -474
- package/dist/monitor.runtime-BCCa6qTX.js +0 -2
- package/dist/outbound.runtime-D4w0-Gkc.js +0 -3
- package/dist/probe-CM03wo6r.js +0 -28
- package/dist/probe.runtime-D13ZtXsd.js +0 -2
package/dist/runtime-api.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { i as resolveLineAccount, n as normalizeAccountId, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { a as validateLineSignature, c as normalizeAllowFrom, i as parseLineWebhookBody, n as createLineNodeWebhookHandler, o as downloadLineMedia, r as readLineWebhookRequestBody, s as firstDefined, t as monitorLineProvider } from "./monitor-
|
|
5
|
-
import { t as
|
|
6
|
-
import {
|
|
7
|
-
import { _ as replyMessageLine, a as createQuickReplyItems, b as resolveLineChannelAccessToken, c as getUserDisplayName, d as pushImageMessage, f as pushLocationMessage, g as pushTextMessageWithQuickReplies, h as pushTemplateMessage, i as createLocationMessage, l as getUserProfile, m as pushMessagesLine, n as createFlexMessage, o as createTextMessageWithQuickReplies, p as pushMessageLine, r as createImageMessage, s as createVideoMessage, t as createAudioMessage, u as pushFlexMessage, v as sendMessageLine, y as showLoadingAnimation } from "./send-Dce0m539.js";
|
|
1
|
+
import { i as resolveLineAccount, n as normalizeAccountId, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-BBEFMYbY.js";
|
|
2
|
+
import { _ as LineChannelConfigSchema, c as createDeviceControlCard, d as resolveExactLineGroupConfigKey, f as resolveLineGroupConfigEntry, g as setLineRuntime, l as createMediaPlayerCard, m as resolveLineGroupsConfig, p as resolveLineGroupLookupIds, s as createAppleTvRemoteCard, v as LineConfigSchema } from "./rich-messages-DY1AGKAJ.js";
|
|
3
|
+
import { C as toFlexMessage, D as createVideoMessage, E as createImageMessage, T as createAudioMessage, a as probeLineBot, c as createAgendaCard, d as createActionCard, f as createImageCard, g as messageAction, h as datetimePickerAction, l as createEventCard, m as createListCard, o as resolveLineChannelAccessToken, p as createInfoCard, u as createReceiptCard, x as uriAction, y as postbackAction } from "./send-retry-ChpfJ-LM.js";
|
|
4
|
+
import { a as validateLineSignature, c as normalizeAllowFrom, i as parseLineWebhookBody, n as createLineNodeWebhookHandler, o as downloadLineMedia, r as readLineWebhookRequestBody, s as firstDefined, t as monitorLineProvider } from "./monitor-D0fHFXCi.js";
|
|
5
|
+
import { a as stripMarkdown, c as createCarouselColumn, i as processLineMessage, l as createConfirmTemplate, n as convertTableToFlexBubble, o as buildTemplateMessageFromPayload, r as hasMarkdownToConvert, s as createButtonTemplate, t as convertCodeBlockToFlexBubble, u as createTemplateCarousel } from "./markdown-to-line-B-sj-7II.js";
|
|
6
|
+
import { _ as showLoadingAnimation, c as pushFlexMessage, d as pushMessageLine, f as pushMessagesLine, g as sendMessageLine, h as replyMessageLine, i as createTextMessageWithQuickReplies, l as pushImageMessage, m as pushTextMessageWithQuickReplies, n as createLocationMessage, o as getUserDisplayName, p as pushTemplateMessage, r as createQuickReplyItems, s as getUserProfile, t as createFlexMessage, u as pushLocationMessage } from "./send-CQQxpxPp.js";
|
|
8
7
|
import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
|
9
|
-
import {
|
|
8
|
+
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
9
|
+
import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
|
|
10
|
+
import { messagingApi } from "@line/bot-sdk";
|
|
10
11
|
import { DEFAULT_ACCOUNT_ID, formatDocsLink, setSetupChannelEnabled, splitSetupEntries } from "openclaw/plugin-sdk/setup";
|
|
11
12
|
import { buildComputedAccountStatusSnapshot, buildTokenChannelStatusSummary } from "openclaw/plugin-sdk/status-helpers";
|
|
13
|
+
import { clearAccountEntryFields } from "openclaw/plugin-sdk/core";
|
|
12
14
|
import { danger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
13
|
-
import {
|
|
14
|
-
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
15
|
+
import { bufferToBlobPart } from "openclaw/plugin-sdk/blob-runtime";
|
|
15
16
|
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-local-roots";
|
|
16
|
-
import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
|
|
17
17
|
import { loadWebMediaRaw } from "openclaw/plugin-sdk/web-media";
|
|
18
18
|
//#region extensions/line/src/webhook.ts
|
|
19
|
-
const LINE_WEBHOOK_MAX_RAW_BODY_BYTES =
|
|
19
|
+
const LINE_WEBHOOK_MAX_RAW_BODY_BYTES = 65536;
|
|
20
20
|
function readRawBody(req) {
|
|
21
21
|
const rawBody = req.rawBody ?? (typeof req.body === "string" || Buffer.isBuffer(req.body) ? req.body : null);
|
|
22
22
|
if (!rawBody) return null;
|
|
@@ -123,9 +123,8 @@ async function uploadRichMenuImage(richMenuId, imagePath, opts) {
|
|
|
123
123
|
const blobClient = getBlobClient(opts);
|
|
124
124
|
const media = await loadWebMediaRaw(imagePath, { localRoots: opts.mediaLocalRoots ?? getAgentScopedMediaLocalRoots(opts.cfg) });
|
|
125
125
|
const contentType = media.contentType === "image/png" || media.contentType === "image/jpeg" ? media.contentType : mimeTypeFromFilePath(imagePath) === "image/png" ? "image/png" : "image/jpeg";
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
await blobClient.setRichMenuImage(richMenuId, new Blob([imageBytes], { type: contentType }));
|
|
126
|
+
const blob = new Blob([bufferToBlobPart(media.buffer)], { type: contentType });
|
|
127
|
+
await blobClient.setRichMenuImage(richMenuId, blob);
|
|
129
128
|
if (opts.verbose) logVerbose(`line: uploaded image to rich menu ${richMenuId}`);
|
|
130
129
|
}
|
|
131
130
|
async function setDefaultRichMenu(richMenuId, opts) {
|
|
@@ -1,55 +1,80 @@
|
|
|
1
|
-
import { i as resolveLineAccount } from "./accounts-
|
|
2
|
-
import {
|
|
1
|
+
import { i as resolveLineAccount } from "./accounts-BBEFMYbY.js";
|
|
2
|
+
import { g as messageAction, i as runLinePushWithRetries, o as resolveLineChannelAccessToken, s as createLineSendReceipt, v as normalizeLineMessage, w as buildLineMediaMessage } from "./send-retry-ChpfJ-LM.js";
|
|
3
3
|
import { createChannelPartialDeliveryError } from "openclaw/plugin-sdk/channel-inbound";
|
|
4
4
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
5
|
-
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
6
|
-
import { fetchWithRuntimeDispatcherOrMockedGlobal } from "openclaw/plugin-sdk/runtime-fetch";
|
|
7
5
|
import { HTTPFetchError, messagingApi } from "@line/bot-sdk";
|
|
8
|
-
import {
|
|
6
|
+
import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
|
|
7
|
+
import { fetchWithRuntimeDispatcherOrMockedGlobal } from "openclaw/plugin-sdk/runtime-fetch";
|
|
8
|
+
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
10
10
|
import lineBotSdkPackage from "@line/bot-sdk/package.json" with { type: "json" };
|
|
11
11
|
import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
|
|
12
12
|
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
13
13
|
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
import { createDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
|
|
15
|
+
//#region extensions/line/src/outbound-message-log.ts
|
|
16
|
+
const RECENT_SENT_LIMIT = 500;
|
|
17
|
+
const recentSentByAccount = /* @__PURE__ */ new Map();
|
|
18
|
+
function recentSentFor(accountId) {
|
|
19
|
+
const existing = recentSentByAccount.get(accountId);
|
|
20
|
+
if (existing) return existing;
|
|
21
|
+
const created = createDedupeCache({
|
|
22
|
+
ttlMs: 0,
|
|
23
|
+
maxSize: RECENT_SENT_LIMIT
|
|
24
|
+
});
|
|
25
|
+
recentSentByAccount.set(accountId, created);
|
|
26
|
+
return created;
|
|
27
|
+
}
|
|
28
|
+
function recordLineSentMessages(accountId, messageIds) {
|
|
29
|
+
if (messageIds.length === 0) return;
|
|
30
|
+
const recentSent = recentSentFor(accountId);
|
|
31
|
+
for (const messageId of messageIds) recentSent.check(messageId);
|
|
32
|
+
}
|
|
33
|
+
function quotesLineBotMessage(accountId, quotedMessageId) {
|
|
34
|
+
return recentSentByAccount.get(accountId)?.peek(quotedMessageId) ?? false;
|
|
21
35
|
}
|
|
22
|
-
//#endregion
|
|
23
|
-
//#region extensions/line/src/send-retry.ts
|
|
24
|
-
function isRetryableLinePushError(error) {
|
|
25
|
-
const candidates = collectErrorGraphCandidates(error, (candidate) => [candidate.cause, candidate.error]);
|
|
26
|
-
const httpError = candidates.find((candidate) => candidate instanceof HTTPFetchError);
|
|
27
|
-
if (httpError) return httpError.status >= 500;
|
|
28
|
-
return candidates.some((candidate) => classifyTransientNetworkErrorCode(extractErrorCode(candidate)) !== void 0);
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Pushes are non-idempotent without a retry key, so the generic message-matching
|
|
32
|
-
* fallback stays off and only the classification above may replay a request.
|
|
33
|
-
*/
|
|
34
|
-
const runLinePushWithRetries = createChannelApiRetryRunner({
|
|
35
|
-
shouldRetry: isRetryableLinePushError,
|
|
36
|
-
strictShouldRetry: true,
|
|
37
|
-
verbose: true
|
|
38
|
-
});
|
|
39
36
|
//#endregion
|
|
40
37
|
//#region extensions/line/src/send.ts
|
|
41
|
-
const
|
|
42
|
-
|
|
38
|
+
const profileCache = {
|
|
39
|
+
values: /* @__PURE__ */ new Map(),
|
|
40
|
+
pending: /* @__PURE__ */ new Map()
|
|
41
|
+
};
|
|
42
|
+
const groupNameCache = {
|
|
43
|
+
values: /* @__PURE__ */ new Map(),
|
|
44
|
+
pending: /* @__PURE__ */ new Map()
|
|
45
|
+
};
|
|
46
|
+
const PROFILE_CACHE_TTL_MS = 3e5;
|
|
43
47
|
const PROFILE_CACHE_MAX_ENTRIES = 1e3;
|
|
44
48
|
const LINE_FLEX_ALT_TEXT_LIMIT = 1500;
|
|
45
49
|
const LINE_LOCATION_LABEL_LIMIT = 100;
|
|
46
|
-
const LINE_PROVIDER_RESPONSE_MAX_BYTES =
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
const LINE_PROVIDER_RESPONSE_MAX_BYTES = 16384;
|
|
51
|
+
function rememberLineIdentity(cache, key, value) {
|
|
52
|
+
const entry = {
|
|
53
|
+
value,
|
|
54
|
+
fetchedAt: Date.now()
|
|
55
|
+
};
|
|
56
|
+
cache.values.delete(key);
|
|
57
|
+
cache.values.set(key, entry);
|
|
58
|
+
if (cache.values.size <= PROFILE_CACHE_MAX_ENTRIES) return;
|
|
59
|
+
for (const [cachedKey, cached] of cache.values) if (entry.fetchedAt - cached.fetchedAt >= PROFILE_CACHE_TTL_MS) cache.values.delete(cachedKey);
|
|
60
|
+
pruneMapToMaxSize(cache.values, PROFILE_CACHE_MAX_ENTRIES);
|
|
61
|
+
}
|
|
62
|
+
async function loadLineIdentity(cache, key, load) {
|
|
63
|
+
const cached = cache.values.get(key);
|
|
64
|
+
if (cached && Date.now() - cached.fetchedAt < PROFILE_CACHE_TTL_MS) return cached.value;
|
|
65
|
+
const pending = cache.pending.get(key);
|
|
66
|
+
if (pending) return await pending;
|
|
67
|
+
const lookup = load().then((value) => {
|
|
68
|
+
rememberLineIdentity(cache, key, value);
|
|
69
|
+
return value;
|
|
70
|
+
});
|
|
71
|
+
cache.pending.set(key, lookup);
|
|
72
|
+
pruneMapToMaxSize(cache.pending, PROFILE_CACHE_MAX_ENTRIES);
|
|
73
|
+
try {
|
|
74
|
+
return await lookup;
|
|
75
|
+
} finally {
|
|
76
|
+
if (cache.pending.get(key) === lookup) cache.pending.delete(key);
|
|
77
|
+
}
|
|
53
78
|
}
|
|
54
79
|
function resolveLineProviderMessageIds(response, operation) {
|
|
55
80
|
const sentMessages = Array.isArray(response?.sentMessages) ? response.sentMessages : [];
|
|
@@ -76,12 +101,10 @@ function normalizeTarget(to) {
|
|
|
76
101
|
if (normalized.length >= 33 && !/^[CUR]/.test(normalized)) throw new Error(`Recipient is not a valid LINE id (case-sensitive; expected leading capital C/U/R): ${truncateUtf16Safe(normalized, 4)}…`);
|
|
77
102
|
return normalized;
|
|
78
103
|
}
|
|
79
|
-
function isLineUserChatId(chatId) {
|
|
80
|
-
return /^U/i.test(chatId);
|
|
81
|
-
}
|
|
82
104
|
function resolveLineMessagingAccount(opts) {
|
|
105
|
+
const cfg = requireRuntimeConfig(opts.cfg, "LINE send");
|
|
83
106
|
const account = resolveLineAccount({
|
|
84
|
-
cfg
|
|
107
|
+
cfg,
|
|
85
108
|
accountId: opts.accountId
|
|
86
109
|
});
|
|
87
110
|
return {
|
|
@@ -140,28 +163,6 @@ function createTextMessage(text) {
|
|
|
140
163
|
text
|
|
141
164
|
};
|
|
142
165
|
}
|
|
143
|
-
function createImageMessage(originalContentUrl, previewImageUrl) {
|
|
144
|
-
return {
|
|
145
|
-
type: "image",
|
|
146
|
-
originalContentUrl,
|
|
147
|
-
previewImageUrl: previewImageUrl ?? originalContentUrl
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
function createVideoMessage(originalContentUrl, previewImageUrl, trackingId) {
|
|
151
|
-
return {
|
|
152
|
-
type: "video",
|
|
153
|
-
originalContentUrl,
|
|
154
|
-
previewImageUrl,
|
|
155
|
-
...trackingId ? { trackingId } : {}
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
function createAudioMessage(originalContentUrl, durationMs) {
|
|
159
|
-
return {
|
|
160
|
-
type: "audio",
|
|
161
|
-
originalContentUrl,
|
|
162
|
-
duration: durationMs
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
166
|
function isValidLineLocation(location) {
|
|
166
167
|
return location.title.trim().length > 0 && location.address.trim().length > 0;
|
|
167
168
|
}
|
|
@@ -184,9 +185,13 @@ function createLocationMessage(location) {
|
|
|
184
185
|
function logLineHttpError(err, context) {
|
|
185
186
|
if (!err || typeof err !== "object") return;
|
|
186
187
|
const { status, statusText, body } = err;
|
|
187
|
-
if (typeof body === "string")
|
|
188
|
+
if (typeof body === "string") {
|
|
189
|
+
const summary = status ? `${status} ${statusText ?? ""}`.trim() : "unknown status";
|
|
190
|
+
logVerbose(`line: ${context} failed (${summary}): ${body}`);
|
|
191
|
+
}
|
|
188
192
|
}
|
|
189
193
|
function recordLineOutboundActivity(accountId, delivery) {
|
|
194
|
+
recordLineSentMessages(accountId, delivery.messageIds);
|
|
190
195
|
try {
|
|
191
196
|
recordChannelActivity({
|
|
192
197
|
channel: "line",
|
|
@@ -212,7 +217,7 @@ function resolveLineReceiptKind(messages) {
|
|
|
212
217
|
async function pushLineMessages(to, messages, opts, behavior = {}) {
|
|
213
218
|
if (messages.length === 0) throw new Error("Message must be non-empty for LINE sends");
|
|
214
219
|
const { account, token, chatId } = createLinePushContext(to, opts);
|
|
215
|
-
const normalizedMessages = messages.map(
|
|
220
|
+
const normalizedMessages = messages.map(normalizeLineMessage);
|
|
216
221
|
const retryKey = randomUUID();
|
|
217
222
|
const { messageId, messageIds } = resolveLineProviderMessageIds(await runLinePushWithRetries(async () => {
|
|
218
223
|
try {
|
|
@@ -240,7 +245,10 @@ async function pushLineMessages(to, messages, opts, behavior = {}) {
|
|
|
240
245
|
messageIds,
|
|
241
246
|
receipt: result.receipt
|
|
242
247
|
});
|
|
243
|
-
if (opts.verbose)
|
|
248
|
+
if (opts.verbose) {
|
|
249
|
+
const logMessage = behavior.verboseMessage?.(chatId, messages.length) ?? `line: pushed ${messages.length} messages to ${chatId}`;
|
|
250
|
+
logVerbose(logMessage);
|
|
251
|
+
}
|
|
244
252
|
return result;
|
|
245
253
|
}
|
|
246
254
|
async function replyLineMessages(replyToken, messages, opts) {
|
|
@@ -248,7 +256,7 @@ async function replyLineMessages(replyToken, messages, opts) {
|
|
|
248
256
|
return {
|
|
249
257
|
...resolveLineProviderMessageIds(await sendLineProviderMessages("reply", token, {
|
|
250
258
|
replyToken,
|
|
251
|
-
messages: messages.map(
|
|
259
|
+
messages: messages.map(normalizeLineMessage)
|
|
252
260
|
}), "reply"),
|
|
253
261
|
accountId: account.accountId
|
|
254
262
|
};
|
|
@@ -257,29 +265,12 @@ async function sendMessageLine(to, text, opts) {
|
|
|
257
265
|
const chatId = normalizeTarget(to);
|
|
258
266
|
const messages = [];
|
|
259
267
|
const mediaUrl = opts.mediaUrl?.trim();
|
|
260
|
-
if (mediaUrl) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
await validateLineMediaUrl(previewImageUrl);
|
|
267
|
-
const trackingId = isLineUserChatId(chatId) ? opts.trackingId : void 0;
|
|
268
|
-
messages.push(createVideoMessage(mediaUrl, previewImageUrl, trackingId));
|
|
269
|
-
break;
|
|
270
|
-
}
|
|
271
|
-
case "audio":
|
|
272
|
-
messages.push(createAudioMessage(mediaUrl, opts.durationMs ?? 6e4));
|
|
273
|
-
break;
|
|
274
|
-
default:
|
|
275
|
-
{
|
|
276
|
-
const previewImageUrl = opts.previewImageUrl?.trim() || mediaUrl;
|
|
277
|
-
await validateLineMediaUrl(previewImageUrl);
|
|
278
|
-
messages.push(createImageMessage(mediaUrl, previewImageUrl));
|
|
279
|
-
}
|
|
280
|
-
break;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
268
|
+
if (mediaUrl) messages.push(await buildLineMediaMessage(mediaUrl, {
|
|
269
|
+
mediaKind: opts.mediaKind,
|
|
270
|
+
previewImageUrl: opts.previewImageUrl,
|
|
271
|
+
durationMs: opts.durationMs,
|
|
272
|
+
trackingId: opts.trackingId
|
|
273
|
+
}, chatId));
|
|
283
274
|
if (text?.trim()) messages.push(createTextMessage(text.trim()));
|
|
284
275
|
if (messages.length === 0) throw new Error("Message must be non-empty for LINE sends");
|
|
285
276
|
if (opts.replyToken) {
|
|
@@ -326,9 +317,10 @@ function createFlexMessage(altText, contents) {
|
|
|
326
317
|
};
|
|
327
318
|
}
|
|
328
319
|
async function pushImageMessage(to, originalContentUrl, previewImageUrl, opts) {
|
|
329
|
-
await
|
|
330
|
-
|
|
331
|
-
|
|
320
|
+
return pushLineMessages(to, [await buildLineMediaMessage(originalContentUrl, {
|
|
321
|
+
mediaKind: "image",
|
|
322
|
+
previewImageUrl
|
|
323
|
+
}, to)], opts, { verboseMessage: (chatId) => `line: pushed image to ${chatId}` });
|
|
332
324
|
}
|
|
333
325
|
async function pushLocationMessage(to, location, opts) {
|
|
334
326
|
return pushLineMessages(to, [createLocationMessage(location)], opts, { verboseMessage: (chatId) => `line: pushed location to ${chatId}` });
|
|
@@ -370,26 +362,43 @@ async function showLoadingAnimation(chatId, opts) {
|
|
|
370
362
|
logVerbose(`line: loading animation failed (non-fatal): ${String(err)}`);
|
|
371
363
|
}
|
|
372
364
|
}
|
|
365
|
+
function lineProfileCacheKey(accountId, userId, scope) {
|
|
366
|
+
const conversation = scope.groupId ? ["group", scope.groupId] : scope.roomId ? ["room", scope.roomId] : ["direct"];
|
|
367
|
+
return JSON.stringify([
|
|
368
|
+
accountId,
|
|
369
|
+
"profile",
|
|
370
|
+
...conversation,
|
|
371
|
+
userId
|
|
372
|
+
]);
|
|
373
|
+
}
|
|
374
|
+
function fetchLineMemberProfile(client, userId, scope) {
|
|
375
|
+
if (scope.groupId) return client.getGroupMemberProfile(scope.groupId, userId);
|
|
376
|
+
if (scope.roomId) return client.getRoomMemberProfile(scope.roomId, userId);
|
|
377
|
+
return client.getProfile(userId);
|
|
378
|
+
}
|
|
373
379
|
async function getUserProfile(userId, opts) {
|
|
374
|
-
|
|
375
|
-
const cached = userProfileCache.get(userId);
|
|
376
|
-
if (cached && Date.now() - cached.fetchedAt < PROFILE_CACHE_TTL_MS) return {
|
|
377
|
-
displayName: cached.displayName,
|
|
378
|
-
pictureUrl: cached.pictureUrl
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
const { client } = createLineMessagingClient(opts);
|
|
380
|
+
const useCache = opts.useCache ?? true;
|
|
382
381
|
try {
|
|
383
|
-
const
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
382
|
+
const { account, token } = resolveLineMessagingAccount(opts);
|
|
383
|
+
const cacheKey = lineProfileCacheKey(account.accountId, userId, opts);
|
|
384
|
+
const load = async () => {
|
|
385
|
+
try {
|
|
386
|
+
const profile = await fetchLineMemberProfile(new messagingApi.MessagingApiClient({ channelAccessToken: token }), userId, opts);
|
|
387
|
+
return {
|
|
388
|
+
displayName: profile.displayName,
|
|
389
|
+
pictureUrl: profile.pictureUrl
|
|
390
|
+
};
|
|
391
|
+
} catch (err) {
|
|
392
|
+
logVerbose(`line: failed to fetch profile for ${userId}: ${String(err)}`);
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
387
395
|
};
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
396
|
+
if (!useCache) {
|
|
397
|
+
const profile = await load();
|
|
398
|
+
rememberLineIdentity(profileCache, cacheKey, profile);
|
|
399
|
+
return profile;
|
|
400
|
+
}
|
|
401
|
+
return await loadLineIdentity(profileCache, cacheKey, load);
|
|
393
402
|
} catch (err) {
|
|
394
403
|
logVerbose(`line: failed to fetch profile for ${userId}: ${String(err)}`);
|
|
395
404
|
return null;
|
|
@@ -398,5 +407,26 @@ async function getUserProfile(userId, opts) {
|
|
|
398
407
|
async function getUserDisplayName(userId, opts) {
|
|
399
408
|
return (await getUserProfile(userId, opts))?.displayName ?? userId;
|
|
400
409
|
}
|
|
410
|
+
async function getLineGroupName(groupId, opts) {
|
|
411
|
+
try {
|
|
412
|
+
const { account, token } = resolveLineMessagingAccount(opts);
|
|
413
|
+
const cacheKey = JSON.stringify([
|
|
414
|
+
account.accountId,
|
|
415
|
+
"group",
|
|
416
|
+
groupId
|
|
417
|
+
]);
|
|
418
|
+
return await loadLineIdentity(groupNameCache, cacheKey, async () => {
|
|
419
|
+
try {
|
|
420
|
+
return (await new messagingApi.MessagingApiClient({ channelAccessToken: token }).getGroupSummary(groupId)).groupName.trim() || void 0;
|
|
421
|
+
} catch (err) {
|
|
422
|
+
logVerbose(`line: failed to fetch group summary for ${groupId}: ${String(err)}`);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
} catch (err) {
|
|
427
|
+
logVerbose(`line: failed to fetch group summary for ${groupId}: ${String(err)}`);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
401
431
|
//#endregion
|
|
402
|
-
export {
|
|
432
|
+
export { showLoadingAnimation as _, getLineGroupName as a, pushFlexMessage as c, pushMessageLine as d, pushMessagesLine as f, sendMessageLine as g, replyMessageLine as h, createTextMessageWithQuickReplies as i, pushImageMessage as l, pushTextMessageWithQuickReplies as m, createLocationMessage as n, getUserDisplayName as o, pushTemplateMessage as p, createQuickReplyItems as r, getUserProfile as s, createFlexMessage as t, pushLocationMessage as u, quotesLineBotMessage as v };
|