@gakr-gakr/line 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api.ts +11 -0
- package/autobot.plugin.json +15 -0
- package/channel-plugin-api.ts +1 -0
- package/contract-api.ts +5 -0
- package/index.ts +54 -0
- package/package.json +60 -0
- package/runtime-api.ts +182 -0
- package/secret-contract-api.ts +4 -0
- package/setup-api.ts +2 -0
- package/setup-entry.ts +9 -0
- package/src/account-helpers.ts +16 -0
- package/src/accounts.ts +187 -0
- package/src/actions.ts +61 -0
- package/src/auto-reply-delivery.ts +200 -0
- package/src/bindings.ts +65 -0
- package/src/bot-access.ts +30 -0
- package/src/bot-handlers.ts +620 -0
- package/src/bot-message-context.ts +586 -0
- package/src/bot.ts +70 -0
- package/src/card-command.ts +347 -0
- package/src/channel-access-token.ts +14 -0
- package/src/channel-api.ts +17 -0
- package/src/channel-shared.ts +48 -0
- package/src/channel.runtime.ts +3 -0
- package/src/channel.setup.ts +11 -0
- package/src/channel.ts +155 -0
- package/src/config-adapter.ts +29 -0
- package/src/config-schema.ts +81 -0
- package/src/download.ts +34 -0
- package/src/flex-templates/basic-cards.ts +395 -0
- package/src/flex-templates/common.ts +20 -0
- package/src/flex-templates/media-control-cards.ts +555 -0
- package/src/flex-templates/message.ts +13 -0
- package/src/flex-templates/schedule-cards.ts +467 -0
- package/src/flex-templates/types.ts +22 -0
- package/src/flex-templates.ts +32 -0
- package/src/gateway.ts +129 -0
- package/src/group-keys.ts +65 -0
- package/src/group-policy.ts +22 -0
- package/src/markdown-to-line.ts +416 -0
- package/src/monitor-durable.ts +37 -0
- package/src/monitor.runtime.ts +1 -0
- package/src/monitor.ts +507 -0
- package/src/outbound-media.ts +120 -0
- package/src/outbound.runtime.ts +12 -0
- package/src/outbound.ts +427 -0
- package/src/probe.runtime.ts +1 -0
- package/src/probe.ts +34 -0
- package/src/quick-reply-fallback.ts +10 -0
- package/src/reply-chunks.ts +110 -0
- package/src/reply-payload-transform.ts +317 -0
- package/src/rich-menu.ts +326 -0
- package/src/runtime.ts +32 -0
- package/src/send-receipt.ts +32 -0
- package/src/send.ts +531 -0
- package/src/setup-core.ts +149 -0
- package/src/setup-runtime-api.ts +9 -0
- package/src/setup-surface.ts +229 -0
- package/src/signature.ts +24 -0
- package/src/status.ts +37 -0
- package/src/template-messages.ts +333 -0
- package/src/types.ts +130 -0
- package/src/webhook-node.ts +155 -0
- package/src/webhook-utils.ts +10 -0
- package/src/webhook.ts +135 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { resolveChannelGroupRequireMention } from "autobot/plugin-sdk/channel-policy";
|
|
2
|
+
import { resolveExactLineGroupConfigKey, type AutoBotConfig } from "./channel-api.js";
|
|
3
|
+
|
|
4
|
+
type LineGroupContext = {
|
|
5
|
+
cfg: AutoBotConfig;
|
|
6
|
+
accountId?: string | null;
|
|
7
|
+
groupId?: string | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function resolveLineGroupRequireMention(params: LineGroupContext): boolean {
|
|
11
|
+
const exactGroupId = resolveExactLineGroupConfigKey({
|
|
12
|
+
cfg: params.cfg,
|
|
13
|
+
accountId: params.accountId,
|
|
14
|
+
groupId: params.groupId,
|
|
15
|
+
});
|
|
16
|
+
return resolveChannelGroupRequireMention({
|
|
17
|
+
cfg: params.cfg,
|
|
18
|
+
channel: "line",
|
|
19
|
+
groupId: exactGroupId ?? params.groupId,
|
|
20
|
+
accountId: params.accountId,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import type { messagingApi } from "@line/bot-sdk";
|
|
2
|
+
import { stripMarkdown } from "autobot/plugin-sdk/text-chunking";
|
|
3
|
+
import { createReceiptCard, toFlexMessage, type FlexBubble } from "./flex-templates.js";
|
|
4
|
+
export { stripMarkdown } from "autobot/plugin-sdk/text-chunking";
|
|
5
|
+
|
|
6
|
+
type FlexMessage = messagingApi.FlexMessage;
|
|
7
|
+
type FlexComponent = messagingApi.FlexComponent;
|
|
8
|
+
type FlexText = messagingApi.FlexText;
|
|
9
|
+
type FlexBox = messagingApi.FlexBox;
|
|
10
|
+
|
|
11
|
+
export interface ProcessedLineMessage {
|
|
12
|
+
/** The processed text with markdown stripped */
|
|
13
|
+
text: string;
|
|
14
|
+
/** Flex messages extracted from tables/code blocks */
|
|
15
|
+
flexMessages: FlexMessage[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Regex patterns for markdown detection
|
|
20
|
+
*/
|
|
21
|
+
const MARKDOWN_TABLE_REGEX = /^\|(.+)\|[\r\n]+\|[-:\s|]+\|[\r\n]+((?:\|.+\|[\r\n]*)+)/gm;
|
|
22
|
+
const MARKDOWN_CODE_BLOCK_REGEX = /```(\w*)\n([\s\S]*?)```/g;
|
|
23
|
+
const MARKDOWN_LINK_REGEX = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Detect and extract markdown tables from text
|
|
27
|
+
*/
|
|
28
|
+
export function extractMarkdownTables(text: string): {
|
|
29
|
+
tables: MarkdownTable[];
|
|
30
|
+
textWithoutTables: string;
|
|
31
|
+
} {
|
|
32
|
+
const tables: MarkdownTable[] = [];
|
|
33
|
+
let textWithoutTables = text;
|
|
34
|
+
|
|
35
|
+
// Reset regex state
|
|
36
|
+
MARKDOWN_TABLE_REGEX.lastIndex = 0;
|
|
37
|
+
|
|
38
|
+
let match: RegExpExecArray | null;
|
|
39
|
+
const matches: { fullMatch: string; table: MarkdownTable }[] = [];
|
|
40
|
+
|
|
41
|
+
while ((match = MARKDOWN_TABLE_REGEX.exec(text)) !== null) {
|
|
42
|
+
const fullMatch = match[0];
|
|
43
|
+
const headerLine = match[1];
|
|
44
|
+
const bodyLines = match[2];
|
|
45
|
+
|
|
46
|
+
const headers = parseTableRow(headerLine);
|
|
47
|
+
const rows = bodyLines
|
|
48
|
+
.trim()
|
|
49
|
+
.split(/[\r\n]+/)
|
|
50
|
+
.filter((line) => line.trim())
|
|
51
|
+
.map(parseTableRow);
|
|
52
|
+
|
|
53
|
+
if (headers.length > 0 && rows.length > 0) {
|
|
54
|
+
matches.push({
|
|
55
|
+
fullMatch,
|
|
56
|
+
table: { headers, rows },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Remove tables from text in reverse order to preserve indices
|
|
62
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
63
|
+
const { fullMatch, table } = matches[i];
|
|
64
|
+
tables.unshift(table);
|
|
65
|
+
textWithoutTables = textWithoutTables.replace(fullMatch, "");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { tables, textWithoutTables };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface MarkdownTable {
|
|
72
|
+
headers: string[];
|
|
73
|
+
rows: string[][];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parse a single table row (pipe-separated values)
|
|
78
|
+
*/
|
|
79
|
+
function parseTableRow(row: string): string[] {
|
|
80
|
+
return row
|
|
81
|
+
.split("|")
|
|
82
|
+
.map((cell) => cell.trim())
|
|
83
|
+
.filter((cell, index, arr) => {
|
|
84
|
+
// Filter out empty cells at start/end (from leading/trailing pipes)
|
|
85
|
+
if (index === 0 && cell === "") {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
if (index === arr.length - 1 && cell === "") {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Convert a markdown table to a LINE Flex Message bubble
|
|
97
|
+
*/
|
|
98
|
+
export function convertTableToFlexBubble(table: MarkdownTable): FlexBubble {
|
|
99
|
+
const parseCell = (
|
|
100
|
+
value: string | undefined,
|
|
101
|
+
): { text: string; bold: boolean; hasMarkup: boolean } => {
|
|
102
|
+
const raw = value?.trim() ?? "";
|
|
103
|
+
if (!raw) {
|
|
104
|
+
return { text: "-", bold: false, hasMarkup: false };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let hasMarkup = false;
|
|
108
|
+
const stripped = raw.replace(/\*\*(.+?)\*\*/g, (_, inner) => {
|
|
109
|
+
hasMarkup = true;
|
|
110
|
+
return String(inner);
|
|
111
|
+
});
|
|
112
|
+
const text = stripped.trim() || "-";
|
|
113
|
+
const bold = /^\*\*.+\*\*$/.test(raw);
|
|
114
|
+
|
|
115
|
+
return { text, bold, hasMarkup };
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const headerCells = table.headers.map((header) => parseCell(header));
|
|
119
|
+
const rowCells = table.rows.map((row) => row.map((cell) => parseCell(cell)));
|
|
120
|
+
const hasInlineMarkup =
|
|
121
|
+
headerCells.some((cell) => cell.hasMarkup) ||
|
|
122
|
+
rowCells.some((row) => row.some((cell) => cell.hasMarkup));
|
|
123
|
+
|
|
124
|
+
// For simple 2-column tables, use receipt card format
|
|
125
|
+
if (table.headers.length === 2 && !hasInlineMarkup) {
|
|
126
|
+
const items = rowCells.map((row) => ({
|
|
127
|
+
name: row[0]?.text ?? "-",
|
|
128
|
+
value: row[1]?.text ?? "-",
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
return createReceiptCard({
|
|
132
|
+
title: headerCells.map((cell) => cell.text).join(" / "),
|
|
133
|
+
items,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// For multi-column tables, create a custom layout
|
|
138
|
+
const headerRow: FlexComponent = {
|
|
139
|
+
type: "box",
|
|
140
|
+
layout: "horizontal",
|
|
141
|
+
contents: headerCells.map((cell) => ({
|
|
142
|
+
type: "text",
|
|
143
|
+
text: cell.text,
|
|
144
|
+
weight: "bold",
|
|
145
|
+
size: "sm",
|
|
146
|
+
color: "#333333",
|
|
147
|
+
flex: 1,
|
|
148
|
+
wrap: true,
|
|
149
|
+
})) as FlexText[],
|
|
150
|
+
paddingBottom: "sm",
|
|
151
|
+
} as FlexBox;
|
|
152
|
+
|
|
153
|
+
const dataRows: FlexComponent[] = rowCells.slice(0, 10).map((row, rowIndex) => {
|
|
154
|
+
const rowContents = table.headers.map((_, colIndex) => {
|
|
155
|
+
const cell = row[colIndex] ?? { text: "-", bold: false, hasMarkup: false };
|
|
156
|
+
return {
|
|
157
|
+
type: "text",
|
|
158
|
+
text: cell.text,
|
|
159
|
+
size: "sm",
|
|
160
|
+
color: "#666666",
|
|
161
|
+
flex: 1,
|
|
162
|
+
wrap: true,
|
|
163
|
+
weight: cell.bold ? "bold" : undefined,
|
|
164
|
+
};
|
|
165
|
+
}) as FlexText[];
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
type: "box",
|
|
169
|
+
layout: "horizontal",
|
|
170
|
+
contents: rowContents,
|
|
171
|
+
margin: rowIndex === 0 ? "md" : "sm",
|
|
172
|
+
} as FlexBox;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
type: "bubble",
|
|
177
|
+
body: {
|
|
178
|
+
type: "box",
|
|
179
|
+
layout: "vertical",
|
|
180
|
+
contents: [headerRow, { type: "separator", margin: "sm" }, ...dataRows],
|
|
181
|
+
paddingAll: "lg",
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Detect and extract code blocks from text
|
|
188
|
+
*/
|
|
189
|
+
export function extractCodeBlocks(text: string): {
|
|
190
|
+
codeBlocks: CodeBlock[];
|
|
191
|
+
textWithoutCode: string;
|
|
192
|
+
} {
|
|
193
|
+
const codeBlocks: CodeBlock[] = [];
|
|
194
|
+
let textWithoutCode = text;
|
|
195
|
+
|
|
196
|
+
// Reset regex state
|
|
197
|
+
MARKDOWN_CODE_BLOCK_REGEX.lastIndex = 0;
|
|
198
|
+
|
|
199
|
+
let match: RegExpExecArray | null;
|
|
200
|
+
const matches: { fullMatch: string; block: CodeBlock }[] = [];
|
|
201
|
+
|
|
202
|
+
while ((match = MARKDOWN_CODE_BLOCK_REGEX.exec(text)) !== null) {
|
|
203
|
+
const fullMatch = match[0];
|
|
204
|
+
const language = match[1] || undefined;
|
|
205
|
+
const code = match[2];
|
|
206
|
+
|
|
207
|
+
matches.push({
|
|
208
|
+
fullMatch,
|
|
209
|
+
block: { language, code: code.trim() },
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Remove code blocks in reverse order
|
|
214
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
215
|
+
const { fullMatch, block } = matches[i];
|
|
216
|
+
codeBlocks.unshift(block);
|
|
217
|
+
textWithoutCode = textWithoutCode.replace(fullMatch, "");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return { codeBlocks, textWithoutCode };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export interface CodeBlock {
|
|
224
|
+
language?: string;
|
|
225
|
+
code: string;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Convert a code block to a LINE Flex Message bubble
|
|
230
|
+
*/
|
|
231
|
+
export function convertCodeBlockToFlexBubble(block: CodeBlock): FlexBubble {
|
|
232
|
+
const titleText = block.language ? `Code (${block.language})` : "Code";
|
|
233
|
+
|
|
234
|
+
// Truncate very long code to fit LINE's limits
|
|
235
|
+
const displayCode = block.code.length > 2000 ? block.code.slice(0, 2000) + "\n..." : block.code;
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
type: "bubble",
|
|
239
|
+
body: {
|
|
240
|
+
type: "box",
|
|
241
|
+
layout: "vertical",
|
|
242
|
+
contents: [
|
|
243
|
+
{
|
|
244
|
+
type: "text",
|
|
245
|
+
text: titleText,
|
|
246
|
+
weight: "bold",
|
|
247
|
+
size: "sm",
|
|
248
|
+
color: "#666666",
|
|
249
|
+
} as FlexText,
|
|
250
|
+
{
|
|
251
|
+
type: "box",
|
|
252
|
+
layout: "vertical",
|
|
253
|
+
contents: [
|
|
254
|
+
{
|
|
255
|
+
type: "text",
|
|
256
|
+
text: displayCode,
|
|
257
|
+
size: "xs",
|
|
258
|
+
color: "#333333",
|
|
259
|
+
wrap: true,
|
|
260
|
+
} as FlexText,
|
|
261
|
+
],
|
|
262
|
+
backgroundColor: "#F5F5F5",
|
|
263
|
+
paddingAll: "md",
|
|
264
|
+
cornerRadius: "md",
|
|
265
|
+
margin: "sm",
|
|
266
|
+
} as FlexBox,
|
|
267
|
+
],
|
|
268
|
+
paddingAll: "lg",
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Extract markdown links from text
|
|
275
|
+
*/
|
|
276
|
+
export function extractLinks(text: string): { links: MarkdownLink[]; textWithLinks: string } {
|
|
277
|
+
const links: MarkdownLink[] = [];
|
|
278
|
+
|
|
279
|
+
// Reset regex state
|
|
280
|
+
MARKDOWN_LINK_REGEX.lastIndex = 0;
|
|
281
|
+
|
|
282
|
+
let match: RegExpExecArray | null;
|
|
283
|
+
while ((match = MARKDOWN_LINK_REGEX.exec(text)) !== null) {
|
|
284
|
+
links.push({
|
|
285
|
+
text: match[1],
|
|
286
|
+
url: match[2],
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Replace markdown links with just the text (for plain text output)
|
|
291
|
+
const textWithLinks = text.replace(MARKDOWN_LINK_REGEX, "$1");
|
|
292
|
+
|
|
293
|
+
return { links, textWithLinks };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export interface MarkdownLink {
|
|
297
|
+
text: string;
|
|
298
|
+
url: string;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Create a Flex Message with tappable link buttons
|
|
303
|
+
*/
|
|
304
|
+
export function convertLinksToFlexBubble(links: MarkdownLink[]): FlexBubble {
|
|
305
|
+
const buttons: FlexComponent[] = links.slice(0, 4).map((link, index) => ({
|
|
306
|
+
type: "button",
|
|
307
|
+
action: {
|
|
308
|
+
type: "uri",
|
|
309
|
+
label: link.text.slice(0, 20), // LINE button label limit
|
|
310
|
+
uri: link.url,
|
|
311
|
+
},
|
|
312
|
+
style: index === 0 ? "primary" : "secondary",
|
|
313
|
+
margin: index > 0 ? "sm" : undefined,
|
|
314
|
+
}));
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
type: "bubble",
|
|
318
|
+
body: {
|
|
319
|
+
type: "box",
|
|
320
|
+
layout: "vertical",
|
|
321
|
+
contents: [
|
|
322
|
+
{
|
|
323
|
+
type: "text",
|
|
324
|
+
text: "Links",
|
|
325
|
+
weight: "bold",
|
|
326
|
+
size: "md",
|
|
327
|
+
color: "#333333",
|
|
328
|
+
} as FlexText,
|
|
329
|
+
],
|
|
330
|
+
paddingAll: "lg",
|
|
331
|
+
paddingBottom: "sm",
|
|
332
|
+
},
|
|
333
|
+
footer: {
|
|
334
|
+
type: "box",
|
|
335
|
+
layout: "vertical",
|
|
336
|
+
contents: buttons,
|
|
337
|
+
paddingAll: "md",
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Main function: Process text for LINE output
|
|
344
|
+
* - Extracts tables → Flex Messages
|
|
345
|
+
* - Extracts code blocks → Flex Messages
|
|
346
|
+
* - Strips remaining markdown
|
|
347
|
+
* - Returns processed text + Flex Messages
|
|
348
|
+
*/
|
|
349
|
+
export function processLineMessage(text: string): ProcessedLineMessage {
|
|
350
|
+
const flexMessages: FlexMessage[] = [];
|
|
351
|
+
let processedText = text;
|
|
352
|
+
|
|
353
|
+
// 1. Extract and convert tables
|
|
354
|
+
const { tables, textWithoutTables } = extractMarkdownTables(processedText);
|
|
355
|
+
processedText = textWithoutTables;
|
|
356
|
+
|
|
357
|
+
for (const table of tables) {
|
|
358
|
+
const bubble = convertTableToFlexBubble(table);
|
|
359
|
+
flexMessages.push(toFlexMessage("Table", bubble));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// 2. Extract and convert code blocks
|
|
363
|
+
const { codeBlocks, textWithoutCode } = extractCodeBlocks(processedText);
|
|
364
|
+
processedText = textWithoutCode;
|
|
365
|
+
|
|
366
|
+
for (const block of codeBlocks) {
|
|
367
|
+
const bubble = convertCodeBlockToFlexBubble(block);
|
|
368
|
+
flexMessages.push(toFlexMessage("Code", bubble));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// 3. Handle links - convert [text](url) to plain text for display
|
|
372
|
+
// (We could also create link buttons, but that can get noisy)
|
|
373
|
+
const { textWithLinks } = extractLinks(processedText);
|
|
374
|
+
processedText = textWithLinks;
|
|
375
|
+
|
|
376
|
+
// 4. Strip remaining markdown formatting
|
|
377
|
+
processedText = stripMarkdown(processedText);
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
text: processedText,
|
|
381
|
+
flexMessages,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Check if text contains markdown that needs conversion
|
|
387
|
+
*/
|
|
388
|
+
export function hasMarkdownToConvert(text: string): boolean {
|
|
389
|
+
// Check for tables
|
|
390
|
+
MARKDOWN_TABLE_REGEX.lastIndex = 0;
|
|
391
|
+
if (MARKDOWN_TABLE_REGEX.test(text)) {
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Check for code blocks
|
|
396
|
+
MARKDOWN_CODE_BLOCK_REGEX.lastIndex = 0;
|
|
397
|
+
if (MARKDOWN_CODE_BLOCK_REGEX.test(text)) {
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Check for other markdown patterns
|
|
402
|
+
if (/\*\*[^*]+\*\*/.test(text)) {
|
|
403
|
+
return true;
|
|
404
|
+
} // bold
|
|
405
|
+
if (/~~[^~]+~~/.test(text)) {
|
|
406
|
+
return true;
|
|
407
|
+
} // strikethrough
|
|
408
|
+
if (/^#{1,6}\s+/m.test(text)) {
|
|
409
|
+
return true;
|
|
410
|
+
} // headers
|
|
411
|
+
if (/^>\s+/m.test(text)) {
|
|
412
|
+
return true;
|
|
413
|
+
} // blockquotes
|
|
414
|
+
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { resolveSendableOutboundReplyParts } from "autobot/plugin-sdk/reply-payload";
|
|
2
|
+
import type { ReplyPayload } from "autobot/plugin-sdk/reply-runtime";
|
|
3
|
+
import type { LineChannelData } from "./types.js";
|
|
4
|
+
|
|
5
|
+
export type LineDurableReplyOptions = {
|
|
6
|
+
to: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function hasLineChannelData(payload: ReplyPayload): boolean {
|
|
10
|
+
const lineData = payload.channelData?.line as LineChannelData | undefined;
|
|
11
|
+
return Boolean(lineData && Object.keys(lineData).length > 0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function resolveLineDurableReplyOptions(params: {
|
|
15
|
+
payload: ReplyPayload;
|
|
16
|
+
infoKind: string;
|
|
17
|
+
to: string;
|
|
18
|
+
replyToken?: string | null;
|
|
19
|
+
replyTokenUsed: boolean;
|
|
20
|
+
}): LineDurableReplyOptions | false {
|
|
21
|
+
if (params.infoKind !== "final") {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (params.replyToken && !params.replyTokenUsed) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (hasLineChannelData(params.payload)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const reply = resolveSendableOutboundReplyParts(params.payload);
|
|
31
|
+
if (reply.hasMedia || !reply.hasText) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
to: params.to,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { monitorLineProvider } from "./monitor.js";
|