@line-harness/sdk 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/README.md +127 -0
- package/dist/index.cjs +475 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +448 -0
- package/dist/index.d.ts +448 -0
- package/dist/index.mjs +439 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# @line-harness/sdk
|
|
2
|
+
|
|
3
|
+
AI-native SDK for LINE Harness — programmatic LINE official account automation.
|
|
4
|
+
|
|
5
|
+
Replaces L社/U社 with a fully API-driven approach designed for AI agents (Claude Code).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @line-harness/sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { LineHarness } from '@line-harness/sdk'
|
|
17
|
+
|
|
18
|
+
const client = new LineHarness({
|
|
19
|
+
apiUrl: 'https://your-worker.workers.dev',
|
|
20
|
+
apiKey: 'your-api-key',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// Create a 3-step education scenario
|
|
24
|
+
const scenario = await client.createStepScenario('Welcome Flow', 'friend_add', [
|
|
25
|
+
{ delay: '0m', type: 'text', content: 'Welcome! Thanks for adding us.' },
|
|
26
|
+
{ delay: '1d', type: 'text', content: 'Here are 3 things you should know...' },
|
|
27
|
+
{ delay: '3d', type: 'flex', content: JSON.stringify(flexMessageJson) },
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
// Broadcast to all friends
|
|
31
|
+
await client.broadcastText('Weekend sale! 50% off!')
|
|
32
|
+
|
|
33
|
+
// Broadcast to tagged friends
|
|
34
|
+
await client.broadcastToTag(tagId, 'text', 'VIP exclusive coupon: CODE123')
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## API Reference
|
|
38
|
+
|
|
39
|
+
### Low-Level (1:1 with Worker API)
|
|
40
|
+
|
|
41
|
+
#### Friends
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const { items, total, hasNextPage } = await client.friends.list({ limit: 50, offset: 0 })
|
|
45
|
+
const friend = await client.friends.get(friendId)
|
|
46
|
+
const count = await client.friends.count()
|
|
47
|
+
await client.friends.addTag(friendId, tagId)
|
|
48
|
+
await client.friends.removeTag(friendId, tagId)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### Tags
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const tags = await client.tags.list()
|
|
55
|
+
const tag = await client.tags.create({ name: 'VIP', color: '#EF4444' })
|
|
56
|
+
await client.tags.delete(tagId)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### Scenarios
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const scenarios = await client.scenarios.list()
|
|
63
|
+
const scenario = await client.scenarios.create({ name: 'Flow', triggerType: 'friend_add' })
|
|
64
|
+
const full = await client.scenarios.get(scenarioId)
|
|
65
|
+
await client.scenarios.update(scenarioId, { isActive: false })
|
|
66
|
+
await client.scenarios.delete(scenarioId)
|
|
67
|
+
|
|
68
|
+
// Steps
|
|
69
|
+
await client.scenarios.addStep(scenarioId, {
|
|
70
|
+
stepOrder: 1, delayMinutes: 0, messageType: 'text', messageContent: 'Hello!',
|
|
71
|
+
})
|
|
72
|
+
await client.scenarios.updateStep(scenarioId, stepId, { messageContent: 'Updated' })
|
|
73
|
+
await client.scenarios.deleteStep(scenarioId, stepId)
|
|
74
|
+
|
|
75
|
+
// Enrollment
|
|
76
|
+
await client.scenarios.enroll(scenarioId, friendId)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### Broadcasts
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const broadcasts = await client.broadcasts.list()
|
|
83
|
+
const broadcast = await client.broadcasts.create({
|
|
84
|
+
title: 'Sale', messageType: 'text', messageContent: '50% off!', targetType: 'all',
|
|
85
|
+
})
|
|
86
|
+
await client.broadcasts.update(broadcastId, { scheduledAt: '2026-04-01T09:00:00Z' })
|
|
87
|
+
await client.broadcasts.send(broadcastId)
|
|
88
|
+
await client.broadcasts.delete(broadcastId)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### High-Level (Convenience)
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
// Create scenario with steps in one call (delay: '0m', '1h', '1d', '1w')
|
|
95
|
+
await client.createStepScenario(name, triggerType, steps)
|
|
96
|
+
|
|
97
|
+
// Broadcast text to all
|
|
98
|
+
await client.broadcastText('Message')
|
|
99
|
+
|
|
100
|
+
// Broadcast to tagged friends
|
|
101
|
+
await client.broadcastToTag(tagId, 'text', 'Message')
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Error Handling
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { LineHarness, LineHarnessError } from '@line-harness/sdk'
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
await client.scenarios.get('nonexistent')
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (err instanceof LineHarnessError) {
|
|
113
|
+
console.log(err.status) // 404
|
|
114
|
+
console.log(err.message) // 'Scenario not found'
|
|
115
|
+
console.log(err.endpoint) // 'GET /api/scenarios/nonexistent'
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Requirements
|
|
121
|
+
|
|
122
|
+
- Node.js 18+ (uses native `fetch`)
|
|
123
|
+
- A deployed LINE Harness Worker API
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BroadcastsResource: () => BroadcastsResource,
|
|
24
|
+
FormsResource: () => FormsResource,
|
|
25
|
+
FriendsResource: () => FriendsResource,
|
|
26
|
+
LineHarness: () => LineHarness,
|
|
27
|
+
LineHarnessError: () => LineHarnessError,
|
|
28
|
+
RichMenusResource: () => RichMenusResource,
|
|
29
|
+
ScenariosResource: () => ScenariosResource,
|
|
30
|
+
TagsResource: () => TagsResource,
|
|
31
|
+
TrackedLinksResource: () => TrackedLinksResource,
|
|
32
|
+
parseDelay: () => parseDelay
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(index_exports);
|
|
35
|
+
|
|
36
|
+
// src/errors.ts
|
|
37
|
+
var LineHarnessError = class extends Error {
|
|
38
|
+
constructor(message, status, endpoint) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.status = status;
|
|
41
|
+
this.endpoint = endpoint;
|
|
42
|
+
this.name = "LineHarnessError";
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/http.ts
|
|
47
|
+
var HttpClient = class {
|
|
48
|
+
baseUrl;
|
|
49
|
+
apiKey;
|
|
50
|
+
timeout;
|
|
51
|
+
constructor(config) {
|
|
52
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
53
|
+
this.apiKey = config.apiKey;
|
|
54
|
+
this.timeout = config.timeout;
|
|
55
|
+
}
|
|
56
|
+
async get(path) {
|
|
57
|
+
return this.request("GET", path);
|
|
58
|
+
}
|
|
59
|
+
async post(path, body) {
|
|
60
|
+
return this.request("POST", path, body);
|
|
61
|
+
}
|
|
62
|
+
async put(path, body) {
|
|
63
|
+
return this.request("PUT", path, body);
|
|
64
|
+
}
|
|
65
|
+
async delete(path) {
|
|
66
|
+
return this.request("DELETE", path);
|
|
67
|
+
}
|
|
68
|
+
async request(method, path, body) {
|
|
69
|
+
const url = `${this.baseUrl}${path}`;
|
|
70
|
+
const headers = {
|
|
71
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
72
|
+
"Content-Type": "application/json"
|
|
73
|
+
};
|
|
74
|
+
const options = {
|
|
75
|
+
method,
|
|
76
|
+
headers,
|
|
77
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
78
|
+
};
|
|
79
|
+
if (body !== void 0) {
|
|
80
|
+
options.body = JSON.stringify(body);
|
|
81
|
+
}
|
|
82
|
+
const res = await fetch(url, options);
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
let errorMessage = `HTTP ${res.status}`;
|
|
85
|
+
try {
|
|
86
|
+
const errorBody = await res.json();
|
|
87
|
+
if (errorBody.error) errorMessage = errorBody.error;
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
throw new LineHarnessError(errorMessage, res.status, `${method} ${path}`);
|
|
91
|
+
}
|
|
92
|
+
return res.json();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/resources/friends.ts
|
|
97
|
+
var FriendsResource = class {
|
|
98
|
+
constructor(http, defaultAccountId) {
|
|
99
|
+
this.http = http;
|
|
100
|
+
this.defaultAccountId = defaultAccountId;
|
|
101
|
+
}
|
|
102
|
+
async list(params) {
|
|
103
|
+
const query = new URLSearchParams();
|
|
104
|
+
if (params?.limit !== void 0) query.set("limit", String(params.limit));
|
|
105
|
+
if (params?.offset !== void 0) query.set("offset", String(params.offset));
|
|
106
|
+
if (params?.tagId) query.set("tagId", params.tagId);
|
|
107
|
+
const accountId = params?.accountId ?? this.defaultAccountId;
|
|
108
|
+
if (accountId) query.set("lineAccountId", accountId);
|
|
109
|
+
const qs = query.toString();
|
|
110
|
+
const path = qs ? `/api/friends?${qs}` : "/api/friends";
|
|
111
|
+
const res = await this.http.get(path);
|
|
112
|
+
return res.data;
|
|
113
|
+
}
|
|
114
|
+
async get(id) {
|
|
115
|
+
const res = await this.http.get(`/api/friends/${id}`);
|
|
116
|
+
return res.data;
|
|
117
|
+
}
|
|
118
|
+
async count(params) {
|
|
119
|
+
const accountId = params?.accountId ?? this.defaultAccountId;
|
|
120
|
+
const path = accountId ? `/api/friends/count?lineAccountId=${encodeURIComponent(accountId)}` : "/api/friends/count";
|
|
121
|
+
const res = await this.http.get(path);
|
|
122
|
+
return res.data.count;
|
|
123
|
+
}
|
|
124
|
+
async addTag(friendId, tagId) {
|
|
125
|
+
await this.http.post(`/api/friends/${friendId}/tags`, { tagId });
|
|
126
|
+
}
|
|
127
|
+
async removeTag(friendId, tagId) {
|
|
128
|
+
await this.http.delete(`/api/friends/${friendId}/tags/${tagId}`);
|
|
129
|
+
}
|
|
130
|
+
async sendMessage(friendId, content, messageType = "text") {
|
|
131
|
+
const res = await this.http.post(`/api/friends/${friendId}/messages`, {
|
|
132
|
+
messageType,
|
|
133
|
+
content
|
|
134
|
+
});
|
|
135
|
+
return res.data;
|
|
136
|
+
}
|
|
137
|
+
async setMetadata(friendId, fields) {
|
|
138
|
+
const res = await this.http.put(`/api/friends/${friendId}/metadata`, fields);
|
|
139
|
+
return res.data;
|
|
140
|
+
}
|
|
141
|
+
async setRichMenu(friendId, richMenuId) {
|
|
142
|
+
await this.http.post(`/api/friends/${friendId}/rich-menu`, { richMenuId });
|
|
143
|
+
}
|
|
144
|
+
async removeRichMenu(friendId) {
|
|
145
|
+
await this.http.delete(`/api/friends/${friendId}/rich-menu`);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// src/resources/tags.ts
|
|
150
|
+
var TagsResource = class {
|
|
151
|
+
constructor(http) {
|
|
152
|
+
this.http = http;
|
|
153
|
+
}
|
|
154
|
+
async list() {
|
|
155
|
+
const res = await this.http.get("/api/tags");
|
|
156
|
+
return res.data;
|
|
157
|
+
}
|
|
158
|
+
async create(input) {
|
|
159
|
+
const res = await this.http.post("/api/tags", input);
|
|
160
|
+
return res.data;
|
|
161
|
+
}
|
|
162
|
+
async delete(id) {
|
|
163
|
+
await this.http.delete(`/api/tags/${id}`);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// src/resources/scenarios.ts
|
|
168
|
+
var ScenariosResource = class {
|
|
169
|
+
constructor(http, defaultAccountId) {
|
|
170
|
+
this.http = http;
|
|
171
|
+
this.defaultAccountId = defaultAccountId;
|
|
172
|
+
}
|
|
173
|
+
async list(params) {
|
|
174
|
+
const accountId = params?.accountId ?? this.defaultAccountId;
|
|
175
|
+
const query = accountId ? `?lineAccountId=${accountId}` : "";
|
|
176
|
+
const res = await this.http.get(`/api/scenarios${query}`);
|
|
177
|
+
return res.data;
|
|
178
|
+
}
|
|
179
|
+
async get(id) {
|
|
180
|
+
const res = await this.http.get(`/api/scenarios/${id}`);
|
|
181
|
+
return res.data;
|
|
182
|
+
}
|
|
183
|
+
async create(input) {
|
|
184
|
+
const body = { ...input };
|
|
185
|
+
if (!body.lineAccountId && this.defaultAccountId) {
|
|
186
|
+
body.lineAccountId = this.defaultAccountId;
|
|
187
|
+
}
|
|
188
|
+
const res = await this.http.post("/api/scenarios", body);
|
|
189
|
+
return res.data;
|
|
190
|
+
}
|
|
191
|
+
async update(id, input) {
|
|
192
|
+
const res = await this.http.put(`/api/scenarios/${id}`, input);
|
|
193
|
+
return res.data;
|
|
194
|
+
}
|
|
195
|
+
async delete(id) {
|
|
196
|
+
await this.http.delete(`/api/scenarios/${id}`);
|
|
197
|
+
}
|
|
198
|
+
async addStep(scenarioId, input) {
|
|
199
|
+
const res = await this.http.post(`/api/scenarios/${scenarioId}/steps`, input);
|
|
200
|
+
return res.data;
|
|
201
|
+
}
|
|
202
|
+
async updateStep(scenarioId, stepId, input) {
|
|
203
|
+
const res = await this.http.put(`/api/scenarios/${scenarioId}/steps/${stepId}`, input);
|
|
204
|
+
return res.data;
|
|
205
|
+
}
|
|
206
|
+
async deleteStep(scenarioId, stepId) {
|
|
207
|
+
await this.http.delete(`/api/scenarios/${scenarioId}/steps/${stepId}`);
|
|
208
|
+
}
|
|
209
|
+
async enroll(scenarioId, friendId) {
|
|
210
|
+
const res = await this.http.post(
|
|
211
|
+
`/api/scenarios/${scenarioId}/enroll/${friendId}`
|
|
212
|
+
);
|
|
213
|
+
return res.data;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// src/resources/broadcasts.ts
|
|
218
|
+
var BroadcastsResource = class {
|
|
219
|
+
constructor(http, defaultAccountId) {
|
|
220
|
+
this.http = http;
|
|
221
|
+
this.defaultAccountId = defaultAccountId;
|
|
222
|
+
}
|
|
223
|
+
async list(params) {
|
|
224
|
+
const accountId = params?.accountId ?? this.defaultAccountId;
|
|
225
|
+
const query = accountId ? `?lineAccountId=${accountId}` : "";
|
|
226
|
+
const res = await this.http.get(`/api/broadcasts${query}`);
|
|
227
|
+
return res.data;
|
|
228
|
+
}
|
|
229
|
+
async get(id) {
|
|
230
|
+
const res = await this.http.get(`/api/broadcasts/${id}`);
|
|
231
|
+
return res.data;
|
|
232
|
+
}
|
|
233
|
+
async create(input) {
|
|
234
|
+
const body = { ...input };
|
|
235
|
+
if (!body.lineAccountId && this.defaultAccountId) {
|
|
236
|
+
body.lineAccountId = this.defaultAccountId;
|
|
237
|
+
}
|
|
238
|
+
const res = await this.http.post("/api/broadcasts", body);
|
|
239
|
+
return res.data;
|
|
240
|
+
}
|
|
241
|
+
async update(id, input) {
|
|
242
|
+
const res = await this.http.put(`/api/broadcasts/${id}`, input);
|
|
243
|
+
return res.data;
|
|
244
|
+
}
|
|
245
|
+
async delete(id) {
|
|
246
|
+
await this.http.delete(`/api/broadcasts/${id}`);
|
|
247
|
+
}
|
|
248
|
+
async send(id) {
|
|
249
|
+
const res = await this.http.post(`/api/broadcasts/${id}/send`);
|
|
250
|
+
return res.data;
|
|
251
|
+
}
|
|
252
|
+
async sendToSegment(id, conditions) {
|
|
253
|
+
const res = await this.http.post(
|
|
254
|
+
`/api/broadcasts/${id}/send-segment`,
|
|
255
|
+
{ conditions }
|
|
256
|
+
);
|
|
257
|
+
return res.data;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// src/resources/rich-menus.ts
|
|
262
|
+
var RichMenusResource = class {
|
|
263
|
+
constructor(http) {
|
|
264
|
+
this.http = http;
|
|
265
|
+
}
|
|
266
|
+
async list() {
|
|
267
|
+
const res = await this.http.get("/api/rich-menus");
|
|
268
|
+
return res.data;
|
|
269
|
+
}
|
|
270
|
+
async create(menu) {
|
|
271
|
+
const res = await this.http.post("/api/rich-menus", menu);
|
|
272
|
+
return res.data;
|
|
273
|
+
}
|
|
274
|
+
async delete(richMenuId) {
|
|
275
|
+
await this.http.delete(`/api/rich-menus/${encodeURIComponent(richMenuId)}`);
|
|
276
|
+
}
|
|
277
|
+
async setDefault(richMenuId) {
|
|
278
|
+
await this.http.post(`/api/rich-menus/${encodeURIComponent(richMenuId)}/default`);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// src/resources/tracked-links.ts
|
|
283
|
+
var TrackedLinksResource = class {
|
|
284
|
+
constructor(http) {
|
|
285
|
+
this.http = http;
|
|
286
|
+
}
|
|
287
|
+
async list() {
|
|
288
|
+
const res = await this.http.get("/api/tracked-links");
|
|
289
|
+
return res.data;
|
|
290
|
+
}
|
|
291
|
+
async create(input) {
|
|
292
|
+
const res = await this.http.post("/api/tracked-links", input);
|
|
293
|
+
return res.data;
|
|
294
|
+
}
|
|
295
|
+
async get(id) {
|
|
296
|
+
const res = await this.http.get(`/api/tracked-links/${id}`);
|
|
297
|
+
return res.data;
|
|
298
|
+
}
|
|
299
|
+
async delete(id) {
|
|
300
|
+
await this.http.delete(`/api/tracked-links/${id}`);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// src/resources/forms.ts
|
|
305
|
+
var FormsResource = class {
|
|
306
|
+
constructor(http) {
|
|
307
|
+
this.http = http;
|
|
308
|
+
}
|
|
309
|
+
async list() {
|
|
310
|
+
const res = await this.http.get("/api/forms");
|
|
311
|
+
return res.data;
|
|
312
|
+
}
|
|
313
|
+
async get(id) {
|
|
314
|
+
const res = await this.http.get(`/api/forms/${id}`);
|
|
315
|
+
return res.data;
|
|
316
|
+
}
|
|
317
|
+
async create(input) {
|
|
318
|
+
const res = await this.http.post("/api/forms", input);
|
|
319
|
+
return res.data;
|
|
320
|
+
}
|
|
321
|
+
async update(id, input) {
|
|
322
|
+
const res = await this.http.put(`/api/forms/${id}`, input);
|
|
323
|
+
return res.data;
|
|
324
|
+
}
|
|
325
|
+
async delete(id) {
|
|
326
|
+
await this.http.delete(`/api/forms/${id}`);
|
|
327
|
+
}
|
|
328
|
+
async getSubmissions(formId) {
|
|
329
|
+
const res = await this.http.get(
|
|
330
|
+
`/api/forms/${formId}/submissions`
|
|
331
|
+
);
|
|
332
|
+
return res.data;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// src/delay.ts
|
|
337
|
+
var MULTIPLIERS = {
|
|
338
|
+
m: 1,
|
|
339
|
+
h: 60,
|
|
340
|
+
d: 1440,
|
|
341
|
+
w: 10080
|
|
342
|
+
};
|
|
343
|
+
function parseDelay(input) {
|
|
344
|
+
const match = input.match(/^(\d+)([mhdw])$/);
|
|
345
|
+
if (!match) {
|
|
346
|
+
throw new Error(`Invalid delay format: "${input}". Use format like "30m", "1h", "1d", "1w".`);
|
|
347
|
+
}
|
|
348
|
+
return Number(match[1]) * MULTIPLIERS[match[2]];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/workflows.ts
|
|
352
|
+
var Workflows = class {
|
|
353
|
+
constructor(friends, scenarios, broadcasts) {
|
|
354
|
+
this.friends = friends;
|
|
355
|
+
this.scenarios = scenarios;
|
|
356
|
+
this.broadcasts = broadcasts;
|
|
357
|
+
}
|
|
358
|
+
async createStepScenario(name, triggerType, steps) {
|
|
359
|
+
const scenario = await this.scenarios.create({ name, triggerType });
|
|
360
|
+
for (let i = 0; i < steps.length; i++) {
|
|
361
|
+
const step = steps[i];
|
|
362
|
+
await this.scenarios.addStep(scenario.id, {
|
|
363
|
+
stepOrder: i + 1,
|
|
364
|
+
delayMinutes: parseDelay(step.delay),
|
|
365
|
+
messageType: step.type,
|
|
366
|
+
messageContent: step.content
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
return this.scenarios.get(scenario.id);
|
|
370
|
+
}
|
|
371
|
+
async broadcastText(text) {
|
|
372
|
+
const broadcast = await this.broadcasts.create({
|
|
373
|
+
title: text.slice(0, 50),
|
|
374
|
+
messageType: "text",
|
|
375
|
+
messageContent: text,
|
|
376
|
+
targetType: "all"
|
|
377
|
+
});
|
|
378
|
+
return this.broadcasts.send(broadcast.id);
|
|
379
|
+
}
|
|
380
|
+
async broadcastToTag(tagId, messageType, content) {
|
|
381
|
+
const broadcast = await this.broadcasts.create({
|
|
382
|
+
title: content.slice(0, 50),
|
|
383
|
+
messageType,
|
|
384
|
+
messageContent: content,
|
|
385
|
+
targetType: "tag",
|
|
386
|
+
targetTagId: tagId
|
|
387
|
+
});
|
|
388
|
+
return this.broadcasts.send(broadcast.id);
|
|
389
|
+
}
|
|
390
|
+
async broadcastToSegment(messageType, content, conditions) {
|
|
391
|
+
const broadcast = await this.broadcasts.create({
|
|
392
|
+
title: content.slice(0, 50),
|
|
393
|
+
messageType,
|
|
394
|
+
messageContent: content,
|
|
395
|
+
targetType: "all"
|
|
396
|
+
});
|
|
397
|
+
return this.broadcasts.sendToSegment(broadcast.id, conditions);
|
|
398
|
+
}
|
|
399
|
+
async sendTextToFriend(friendId, text) {
|
|
400
|
+
return this.friends.sendMessage(friendId, text, "text");
|
|
401
|
+
}
|
|
402
|
+
async sendFlexToFriend(friendId, flexJson) {
|
|
403
|
+
return this.friends.sendMessage(friendId, flexJson, "flex");
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// src/client.ts
|
|
408
|
+
var LineHarness = class {
|
|
409
|
+
friends;
|
|
410
|
+
tags;
|
|
411
|
+
scenarios;
|
|
412
|
+
broadcasts;
|
|
413
|
+
richMenus;
|
|
414
|
+
trackedLinks;
|
|
415
|
+
forms;
|
|
416
|
+
apiUrl;
|
|
417
|
+
defaultAccountId;
|
|
418
|
+
workflows;
|
|
419
|
+
createStepScenario;
|
|
420
|
+
broadcastText;
|
|
421
|
+
broadcastToTag;
|
|
422
|
+
broadcastToSegment;
|
|
423
|
+
sendTextToFriend;
|
|
424
|
+
sendFlexToFriend;
|
|
425
|
+
constructor(config) {
|
|
426
|
+
this.apiUrl = config.apiUrl.replace(/\/$/, "");
|
|
427
|
+
this.defaultAccountId = config.lineAccountId;
|
|
428
|
+
const http = new HttpClient({
|
|
429
|
+
baseUrl: this.apiUrl,
|
|
430
|
+
apiKey: config.apiKey,
|
|
431
|
+
timeout: config.timeout ?? 3e4
|
|
432
|
+
});
|
|
433
|
+
this.friends = new FriendsResource(http, this.defaultAccountId);
|
|
434
|
+
this.tags = new TagsResource(http);
|
|
435
|
+
this.scenarios = new ScenariosResource(http, this.defaultAccountId);
|
|
436
|
+
this.broadcasts = new BroadcastsResource(http, this.defaultAccountId);
|
|
437
|
+
this.richMenus = new RichMenusResource(http);
|
|
438
|
+
this.trackedLinks = new TrackedLinksResource(http);
|
|
439
|
+
this.forms = new FormsResource(http);
|
|
440
|
+
this.workflows = new Workflows(this.friends, this.scenarios, this.broadcasts);
|
|
441
|
+
this.createStepScenario = this.workflows.createStepScenario.bind(this.workflows);
|
|
442
|
+
this.broadcastText = this.workflows.broadcastText.bind(this.workflows);
|
|
443
|
+
this.broadcastToTag = this.workflows.broadcastToTag.bind(this.workflows);
|
|
444
|
+
this.broadcastToSegment = this.workflows.broadcastToSegment.bind(this.workflows);
|
|
445
|
+
this.sendTextToFriend = this.workflows.sendTextToFriend.bind(this.workflows);
|
|
446
|
+
this.sendFlexToFriend = this.workflows.sendFlexToFriend.bind(this.workflows);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Generate friend-add URL with OAuth (bot_prompt=aggressive)
|
|
450
|
+
* This URL does friend-add + UUID in one step.
|
|
451
|
+
*
|
|
452
|
+
* @param ref - Attribution code (e.g., 'lp-a', 'instagram', 'seminar-0322')
|
|
453
|
+
* @param redirect - URL to redirect after completion
|
|
454
|
+
*/
|
|
455
|
+
getAuthUrl(options) {
|
|
456
|
+
const url = new URL(`${this.apiUrl}/auth/line`);
|
|
457
|
+
if (options?.ref) url.searchParams.set("ref", options.ref);
|
|
458
|
+
if (options?.redirect) url.searchParams.set("redirect", options.redirect);
|
|
459
|
+
return url.toString();
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
463
|
+
0 && (module.exports = {
|
|
464
|
+
BroadcastsResource,
|
|
465
|
+
FormsResource,
|
|
466
|
+
FriendsResource,
|
|
467
|
+
LineHarness,
|
|
468
|
+
LineHarnessError,
|
|
469
|
+
RichMenusResource,
|
|
470
|
+
ScenariosResource,
|
|
471
|
+
TagsResource,
|
|
472
|
+
TrackedLinksResource,
|
|
473
|
+
parseDelay
|
|
474
|
+
});
|
|
475
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/http.ts","../src/resources/friends.ts","../src/resources/tags.ts","../src/resources/scenarios.ts","../src/resources/broadcasts.ts","../src/resources/rich-menus.ts","../src/resources/tracked-links.ts","../src/resources/forms.ts","../src/delay.ts","../src/workflows.ts","../src/client.ts"],"sourcesContent":["export { LineHarness } from './client.js'\nexport { LineHarnessError } from './errors.js'\nexport { parseDelay } from './delay.js'\n\n// Resource classes (for advanced usage / type narrowing)\nexport { FriendsResource } from './resources/friends.js'\nexport { TagsResource } from './resources/tags.js'\nexport { ScenariosResource } from './resources/scenarios.js'\nexport { BroadcastsResource } from './resources/broadcasts.js'\nexport { RichMenusResource } from './resources/rich-menus.js'\nexport { TrackedLinksResource } from './resources/tracked-links.js'\nexport { FormsResource } from './resources/forms.js'\n\n// All types\nexport type {\n LineHarnessConfig,\n ApiResponse,\n PaginatedData,\n ScenarioTriggerType,\n MessageType,\n BroadcastStatus,\n Friend,\n FriendListParams,\n Tag,\n CreateTagInput,\n Scenario,\n ScenarioListItem,\n ScenarioWithSteps,\n ScenarioStep,\n CreateScenarioInput,\n CreateStepInput,\n UpdateScenarioInput,\n UpdateStepInput,\n FriendScenarioEnrollment,\n Broadcast,\n CreateBroadcastInput,\n UpdateBroadcastInput,\n SegmentRule,\n SegmentCondition,\n StepDefinition,\n RichMenu,\n RichMenuBounds,\n RichMenuAction,\n RichMenuArea,\n CreateRichMenuInput,\n TrackedLink,\n LinkClick,\n TrackedLinkWithClicks,\n CreateTrackedLinkInput,\n FormField,\n Form,\n CreateFormInput,\n UpdateFormInput,\n FormSubmission,\n} from './types.js'\n","export class LineHarnessError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly endpoint: string,\n ) {\n super(message)\n this.name = 'LineHarnessError'\n }\n}\n","import { LineHarnessError } from './errors.js'\n\ninterface HttpClientConfig {\n baseUrl: string\n apiKey: string\n timeout: number\n}\n\nexport class HttpClient {\n private readonly baseUrl: string\n private readonly apiKey: string\n private readonly timeout: number\n\n constructor(config: HttpClientConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, '')\n this.apiKey = config.apiKey\n this.timeout = config.timeout\n }\n\n async get<T = unknown>(path: string): Promise<T> {\n return this.request<T>('GET', path)\n }\n\n async post<T = unknown>(path: string, body?: unknown): Promise<T> {\n return this.request<T>('POST', path, body)\n }\n\n async put<T = unknown>(path: string, body?: unknown): Promise<T> {\n return this.request<T>('PUT', path, body)\n }\n\n async delete<T = unknown>(path: string): Promise<T> {\n return this.request<T>('DELETE', path)\n }\n\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n const url = `${this.baseUrl}${path}`\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n }\n\n const options: RequestInit = {\n method,\n headers,\n signal: AbortSignal.timeout(this.timeout),\n }\n\n if (body !== undefined) {\n options.body = JSON.stringify(body)\n }\n\n const res = await fetch(url, options)\n\n if (!res.ok) {\n let errorMessage = `HTTP ${res.status}`\n try {\n const errorBody = (await res.json()) as { error?: string }\n if (errorBody.error) errorMessage = errorBody.error\n } catch {\n // ignore parse errors\n }\n throw new LineHarnessError(errorMessage, res.status, `${method} ${path}`)\n }\n\n return res.json() as Promise<T>\n }\n}\n","import type { HttpClient } from '../http.js'\nimport type { ApiResponse, PaginatedData, Friend, FriendListParams, MessageType } from '../types.js'\n\nexport class FriendsResource {\n constructor(\n private readonly http: HttpClient,\n private readonly defaultAccountId?: string,\n ) {}\n\n async list(params?: FriendListParams): Promise<PaginatedData<Friend>> {\n const query = new URLSearchParams()\n if (params?.limit !== undefined) query.set('limit', String(params.limit))\n if (params?.offset !== undefined) query.set('offset', String(params.offset))\n if (params?.tagId) query.set('tagId', params.tagId)\n const accountId = params?.accountId ?? this.defaultAccountId\n if (accountId) query.set('lineAccountId', accountId)\n const qs = query.toString()\n const path = qs ? `/api/friends?${qs}` : '/api/friends'\n const res = await this.http.get<ApiResponse<PaginatedData<Friend>>>(path)\n return res.data\n }\n\n async get(id: string): Promise<Friend> {\n const res = await this.http.get<ApiResponse<Friend>>(`/api/friends/${id}`)\n return res.data\n }\n\n async count(params?: { accountId?: string }): Promise<number> {\n const accountId = params?.accountId ?? this.defaultAccountId\n const path = accountId\n ? `/api/friends/count?lineAccountId=${encodeURIComponent(accountId)}`\n : '/api/friends/count'\n const res = await this.http.get<ApiResponse<{ count: number }>>(path)\n return res.data.count\n }\n\n async addTag(friendId: string, tagId: string): Promise<void> {\n await this.http.post(`/api/friends/${friendId}/tags`, { tagId })\n }\n\n async removeTag(friendId: string, tagId: string): Promise<void> {\n await this.http.delete(`/api/friends/${friendId}/tags/${tagId}`)\n }\n\n async sendMessage(friendId: string, content: string, messageType: MessageType = 'text'): Promise<{ messageId: string }> {\n const res = await this.http.post<ApiResponse<{ messageId: string }>>(`/api/friends/${friendId}/messages`, {\n messageType,\n content,\n })\n return res.data\n }\n\n async setMetadata(friendId: string, fields: Record<string, unknown>): Promise<Friend> {\n const res = await this.http.put<ApiResponse<Friend>>(`/api/friends/${friendId}/metadata`, fields)\n return res.data\n }\n\n async setRichMenu(friendId: string, richMenuId: string): Promise<void> {\n await this.http.post(`/api/friends/${friendId}/rich-menu`, { richMenuId })\n }\n\n async removeRichMenu(friendId: string): Promise<void> {\n await this.http.delete(`/api/friends/${friendId}/rich-menu`)\n }\n}\n","import type { HttpClient } from '../http.js'\nimport type { ApiResponse, Tag, CreateTagInput } from '../types.js'\n\nexport class TagsResource {\n constructor(private readonly http: HttpClient) {}\n\n async list(): Promise<Tag[]> {\n const res = await this.http.get<ApiResponse<Tag[]>>('/api/tags')\n return res.data\n }\n\n async create(input: CreateTagInput): Promise<Tag> {\n const res = await this.http.post<ApiResponse<Tag>>('/api/tags', input)\n return res.data\n }\n\n async delete(id: string): Promise<void> {\n await this.http.delete(`/api/tags/${id}`)\n }\n}\n","import type { HttpClient } from '../http.js'\nimport type {\n ApiResponse,\n Scenario,\n ScenarioListItem,\n ScenarioWithSteps,\n ScenarioStep,\n CreateScenarioInput,\n CreateStepInput,\n UpdateScenarioInput,\n UpdateStepInput,\n FriendScenarioEnrollment,\n} from '../types.js'\n\nexport class ScenariosResource {\n constructor(\n private readonly http: HttpClient,\n private readonly defaultAccountId?: string,\n ) {}\n\n async list(params?: { accountId?: string }): Promise<ScenarioListItem[]> {\n const accountId = params?.accountId ?? this.defaultAccountId\n const query = accountId ? `?lineAccountId=${accountId}` : ''\n const res = await this.http.get<ApiResponse<ScenarioListItem[]>>(`/api/scenarios${query}`)\n return res.data\n }\n\n async get(id: string): Promise<ScenarioWithSteps> {\n const res = await this.http.get<ApiResponse<ScenarioWithSteps>>(`/api/scenarios/${id}`)\n return res.data\n }\n\n async create(input: CreateScenarioInput & { lineAccountId?: string }): Promise<Scenario> {\n const body = { ...input }\n if (!body.lineAccountId && this.defaultAccountId) {\n body.lineAccountId = this.defaultAccountId\n }\n const res = await this.http.post<ApiResponse<Scenario>>('/api/scenarios', body)\n return res.data\n }\n\n async update(id: string, input: UpdateScenarioInput): Promise<Scenario> {\n const res = await this.http.put<ApiResponse<Scenario>>(`/api/scenarios/${id}`, input)\n return res.data\n }\n\n async delete(id: string): Promise<void> {\n await this.http.delete(`/api/scenarios/${id}`)\n }\n\n async addStep(scenarioId: string, input: CreateStepInput): Promise<ScenarioStep> {\n const res = await this.http.post<ApiResponse<ScenarioStep>>(`/api/scenarios/${scenarioId}/steps`, input)\n return res.data\n }\n\n async updateStep(scenarioId: string, stepId: string, input: UpdateStepInput): Promise<ScenarioStep> {\n const res = await this.http.put<ApiResponse<ScenarioStep>>(`/api/scenarios/${scenarioId}/steps/${stepId}`, input)\n return res.data\n }\n\n async deleteStep(scenarioId: string, stepId: string): Promise<void> {\n await this.http.delete(`/api/scenarios/${scenarioId}/steps/${stepId}`)\n }\n\n async enroll(scenarioId: string, friendId: string): Promise<FriendScenarioEnrollment> {\n const res = await this.http.post<ApiResponse<FriendScenarioEnrollment>>(\n `/api/scenarios/${scenarioId}/enroll/${friendId}`\n )\n return res.data\n }\n}\n","import type { HttpClient } from '../http.js'\nimport type { ApiResponse, Broadcast, CreateBroadcastInput, UpdateBroadcastInput, SegmentCondition } from '../types.js'\n\nexport class BroadcastsResource {\n constructor(\n private readonly http: HttpClient,\n private readonly defaultAccountId?: string,\n ) {}\n\n async list(params?: { accountId?: string }): Promise<Broadcast[]> {\n const accountId = params?.accountId ?? this.defaultAccountId\n const query = accountId ? `?lineAccountId=${accountId}` : ''\n const res = await this.http.get<ApiResponse<Broadcast[]>>(`/api/broadcasts${query}`)\n return res.data\n }\n\n async get(id: string): Promise<Broadcast> {\n const res = await this.http.get<ApiResponse<Broadcast>>(`/api/broadcasts/${id}`)\n return res.data\n }\n\n async create(input: CreateBroadcastInput & { lineAccountId?: string }): Promise<Broadcast> {\n const body = { ...input }\n if (!body.lineAccountId && this.defaultAccountId) {\n body.lineAccountId = this.defaultAccountId\n }\n const res = await this.http.post<ApiResponse<Broadcast>>('/api/broadcasts', body)\n return res.data\n }\n\n async update(id: string, input: UpdateBroadcastInput): Promise<Broadcast> {\n const res = await this.http.put<ApiResponse<Broadcast>>(`/api/broadcasts/${id}`, input)\n return res.data\n }\n\n async delete(id: string): Promise<void> {\n await this.http.delete(`/api/broadcasts/${id}`)\n }\n\n async send(id: string): Promise<Broadcast> {\n const res = await this.http.post<ApiResponse<Broadcast>>(`/api/broadcasts/${id}/send`)\n return res.data\n }\n\n async sendToSegment(id: string, conditions: SegmentCondition): Promise<Broadcast> {\n const res = await this.http.post<ApiResponse<Broadcast>>(\n `/api/broadcasts/${id}/send-segment`,\n { conditions },\n )\n return res.data\n }\n}\n","import type { HttpClient } from '../http.js'\nimport type { ApiResponse, RichMenu, CreateRichMenuInput } from '../types.js'\n\nexport class RichMenusResource {\n constructor(private readonly http: HttpClient) {}\n\n async list(): Promise<RichMenu[]> {\n const res = await this.http.get<ApiResponse<RichMenu[]>>('/api/rich-menus')\n return res.data\n }\n\n async create(menu: CreateRichMenuInput): Promise<{ richMenuId: string }> {\n const res = await this.http.post<ApiResponse<{ richMenuId: string }>>('/api/rich-menus', menu)\n return res.data\n }\n\n async delete(richMenuId: string): Promise<void> {\n await this.http.delete(`/api/rich-menus/${encodeURIComponent(richMenuId)}`)\n }\n\n async setDefault(richMenuId: string): Promise<void> {\n await this.http.post(`/api/rich-menus/${encodeURIComponent(richMenuId)}/default`)\n }\n}\n","import type { HttpClient } from '../http.js'\nimport type { ApiResponse, TrackedLink, TrackedLinkWithClicks, CreateTrackedLinkInput } from '../types.js'\n\nexport class TrackedLinksResource {\n constructor(private readonly http: HttpClient) {}\n\n async list(): Promise<TrackedLink[]> {\n const res = await this.http.get<ApiResponse<TrackedLink[]>>('/api/tracked-links')\n return res.data\n }\n\n async create(input: CreateTrackedLinkInput): Promise<TrackedLink> {\n const res = await this.http.post<ApiResponse<TrackedLink>>('/api/tracked-links', input)\n return res.data\n }\n\n async get(id: string): Promise<TrackedLinkWithClicks> {\n const res = await this.http.get<ApiResponse<TrackedLinkWithClicks>>(`/api/tracked-links/${id}`)\n return res.data\n }\n\n async delete(id: string): Promise<void> {\n await this.http.delete(`/api/tracked-links/${id}`)\n }\n}\n","import type { HttpClient } from '../http.js'\nimport type {\n ApiResponse,\n Form,\n FormSubmission,\n CreateFormInput,\n UpdateFormInput,\n} from '../types.js'\n\nexport class FormsResource {\n constructor(private readonly http: HttpClient) {}\n\n async list(): Promise<Form[]> {\n const res = await this.http.get<ApiResponse<Form[]>>('/api/forms')\n return res.data\n }\n\n async get(id: string): Promise<Form> {\n const res = await this.http.get<ApiResponse<Form>>(`/api/forms/${id}`)\n return res.data\n }\n\n async create(input: CreateFormInput): Promise<Form> {\n const res = await this.http.post<ApiResponse<Form>>('/api/forms', input)\n return res.data\n }\n\n async update(id: string, input: UpdateFormInput): Promise<Form> {\n const res = await this.http.put<ApiResponse<Form>>(`/api/forms/${id}`, input)\n return res.data\n }\n\n async delete(id: string): Promise<void> {\n await this.http.delete(`/api/forms/${id}`)\n }\n\n async getSubmissions(formId: string): Promise<FormSubmission[]> {\n const res = await this.http.get<ApiResponse<FormSubmission[]>>(\n `/api/forms/${formId}/submissions`,\n )\n return res.data\n }\n}\n","const MULTIPLIERS: Record<string, number> = {\n m: 1,\n h: 60,\n d: 1440,\n w: 10080,\n}\n\nexport function parseDelay(input: string): number {\n const match = input.match(/^(\\d+)([mhdw])$/)\n if (!match) {\n throw new Error(`Invalid delay format: \"${input}\". Use format like \"30m\", \"1h\", \"1d\", \"1w\".`)\n }\n return Number(match[1]) * MULTIPLIERS[match[2]]\n}\n","import type { FriendsResource } from './resources/friends.js'\nimport type { ScenariosResource } from './resources/scenarios.js'\nimport type { BroadcastsResource } from './resources/broadcasts.js'\nimport type { StepDefinition, ScenarioTriggerType, ScenarioWithSteps, Broadcast, MessageType, SegmentCondition } from './types.js'\nimport { parseDelay } from './delay.js'\n\nexport class Workflows {\n constructor(\n private readonly friends: FriendsResource,\n private readonly scenarios: ScenariosResource,\n private readonly broadcasts: BroadcastsResource,\n ) {}\n\n async createStepScenario(\n name: string,\n triggerType: ScenarioTriggerType,\n steps: StepDefinition[],\n ): Promise<ScenarioWithSteps> {\n const scenario = await this.scenarios.create({ name, triggerType })\n\n for (let i = 0; i < steps.length; i++) {\n const step = steps[i]\n await this.scenarios.addStep(scenario.id, {\n stepOrder: i + 1,\n delayMinutes: parseDelay(step.delay),\n messageType: step.type,\n messageContent: step.content,\n })\n }\n\n return this.scenarios.get(scenario.id)\n }\n\n async broadcastText(text: string): Promise<Broadcast> {\n const broadcast = await this.broadcasts.create({\n title: text.slice(0, 50),\n messageType: 'text',\n messageContent: text,\n targetType: 'all',\n })\n return this.broadcasts.send(broadcast.id)\n }\n\n async broadcastToTag(\n tagId: string,\n messageType: MessageType,\n content: string,\n ): Promise<Broadcast> {\n const broadcast = await this.broadcasts.create({\n title: content.slice(0, 50),\n messageType,\n messageContent: content,\n targetType: 'tag',\n targetTagId: tagId,\n })\n return this.broadcasts.send(broadcast.id)\n }\n\n async broadcastToSegment(\n messageType: MessageType,\n content: string,\n conditions: SegmentCondition,\n ): Promise<Broadcast> {\n const broadcast = await this.broadcasts.create({\n title: content.slice(0, 50),\n messageType,\n messageContent: content,\n targetType: 'all',\n })\n return this.broadcasts.sendToSegment(broadcast.id, conditions)\n }\n\n async sendTextToFriend(friendId: string, text: string): Promise<{ messageId: string }> {\n return this.friends.sendMessage(friendId, text, 'text')\n }\n\n async sendFlexToFriend(friendId: string, flexJson: string): Promise<{ messageId: string }> {\n return this.friends.sendMessage(friendId, flexJson, 'flex')\n }\n}\n","import { HttpClient } from './http.js'\nimport { FriendsResource } from './resources/friends.js'\nimport { TagsResource } from './resources/tags.js'\nimport { ScenariosResource } from './resources/scenarios.js'\nimport { BroadcastsResource } from './resources/broadcasts.js'\nimport { RichMenusResource } from './resources/rich-menus.js'\nimport { TrackedLinksResource } from './resources/tracked-links.js'\nimport { FormsResource } from './resources/forms.js'\nimport { Workflows } from './workflows.js'\nimport type { LineHarnessConfig, StepDefinition, ScenarioTriggerType, ScenarioWithSteps, Broadcast, MessageType, SegmentCondition } from './types.js'\n\nexport class LineHarness {\n readonly friends: FriendsResource\n readonly tags: TagsResource\n readonly scenarios: ScenariosResource\n readonly broadcasts: BroadcastsResource\n readonly richMenus: RichMenusResource\n readonly trackedLinks: TrackedLinksResource\n readonly forms: FormsResource\n\n private readonly apiUrl: string\n private readonly defaultAccountId: string | undefined\n private readonly workflows: Workflows\n\n readonly createStepScenario: (name: string, triggerType: ScenarioTriggerType, steps: StepDefinition[]) => Promise<ScenarioWithSteps>\n readonly broadcastText: (text: string) => Promise<Broadcast>\n readonly broadcastToTag: (tagId: string, messageType: MessageType, content: string) => Promise<Broadcast>\n readonly broadcastToSegment: (messageType: MessageType, content: string, conditions: SegmentCondition) => Promise<Broadcast>\n readonly sendTextToFriend: (friendId: string, text: string) => Promise<{ messageId: string }>\n readonly sendFlexToFriend: (friendId: string, flexJson: string) => Promise<{ messageId: string }>\n\n constructor(config: LineHarnessConfig) {\n this.apiUrl = config.apiUrl.replace(/\\/$/, '')\n this.defaultAccountId = config.lineAccountId\n\n const http = new HttpClient({\n baseUrl: this.apiUrl,\n apiKey: config.apiKey,\n timeout: config.timeout ?? 30_000,\n })\n\n this.friends = new FriendsResource(http, this.defaultAccountId)\n this.tags = new TagsResource(http)\n this.scenarios = new ScenariosResource(http, this.defaultAccountId)\n this.broadcasts = new BroadcastsResource(http, this.defaultAccountId)\n this.richMenus = new RichMenusResource(http)\n this.trackedLinks = new TrackedLinksResource(http)\n this.forms = new FormsResource(http)\n this.workflows = new Workflows(this.friends, this.scenarios, this.broadcasts)\n\n this.createStepScenario = this.workflows.createStepScenario.bind(this.workflows)\n this.broadcastText = this.workflows.broadcastText.bind(this.workflows)\n this.broadcastToTag = this.workflows.broadcastToTag.bind(this.workflows)\n this.broadcastToSegment = this.workflows.broadcastToSegment.bind(this.workflows)\n this.sendTextToFriend = this.workflows.sendTextToFriend.bind(this.workflows)\n this.sendFlexToFriend = this.workflows.sendFlexToFriend.bind(this.workflows)\n }\n\n /**\n * Generate friend-add URL with OAuth (bot_prompt=aggressive)\n * This URL does friend-add + UUID in one step.\n *\n * @param ref - Attribution code (e.g., 'lp-a', 'instagram', 'seminar-0322')\n * @param redirect - URL to redirect after completion\n */\n getAuthUrl(options?: { ref?: string; redirect?: string }): string {\n const url = new URL(`${this.apiUrl}/auth/line`)\n if (options?.ref) url.searchParams.set('ref', options.ref)\n if (options?.redirect) url.searchParams.set('redirect', options.redirect)\n return url.toString()\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAC1C,YACE,SACgB,QACA,UAChB;AACA,UAAM,OAAO;AAHG;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;;;ACDO,IAAM,aAAN,MAAiB;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,QAA0B;AACpC,SAAK,UAAU,OAAO,QAAQ,QAAQ,OAAO,EAAE;AAC/C,SAAK,SAAS,OAAO;AACrB,SAAK,UAAU,OAAO;AAAA,EACxB;AAAA,EAEA,MAAM,IAAiB,MAA0B;AAC/C,WAAO,KAAK,QAAW,OAAO,IAAI;AAAA,EACpC;AAAA,EAEA,MAAM,KAAkB,MAAc,MAA4B;AAChE,WAAO,KAAK,QAAW,QAAQ,MAAM,IAAI;AAAA,EAC3C;AAAA,EAEA,MAAM,IAAiB,MAAc,MAA4B;AAC/D,WAAO,KAAK,QAAW,OAAO,MAAM,IAAI;AAAA,EAC1C;AAAA,EAEA,MAAM,OAAoB,MAA0B;AAClD,WAAO,KAAK,QAAW,UAAU,IAAI;AAAA,EACvC;AAAA,EAEA,MAAc,QAAW,QAAgB,MAAc,MAA4B;AACjF,UAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI;AAClC,UAAM,UAAkC;AAAA,MACtC,eAAe,UAAU,KAAK,MAAM;AAAA,MACpC,gBAAgB;AAAA,IAClB;AAEA,UAAM,UAAuB;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,QAAQ,YAAY,QAAQ,KAAK,OAAO;AAAA,IAC1C;AAEA,QAAI,SAAS,QAAW;AACtB,cAAQ,OAAO,KAAK,UAAU,IAAI;AAAA,IACpC;AAEA,UAAM,MAAM,MAAM,MAAM,KAAK,OAAO;AAEpC,QAAI,CAAC,IAAI,IAAI;AACX,UAAI,eAAe,QAAQ,IAAI,MAAM;AACrC,UAAI;AACF,cAAM,YAAa,MAAM,IAAI,KAAK;AAClC,YAAI,UAAU,MAAO,gBAAe,UAAU;AAAA,MAChD,QAAQ;AAAA,MAER;AACA,YAAM,IAAI,iBAAiB,cAAc,IAAI,QAAQ,GAAG,MAAM,IAAI,IAAI,EAAE;AAAA,IAC1E;AAEA,WAAO,IAAI,KAAK;AAAA,EAClB;AACF;;;AChEO,IAAM,kBAAN,MAAsB;AAAA,EAC3B,YACmB,MACA,kBACjB;AAFiB;AACA;AAAA,EAChB;AAAA,EAEH,MAAM,KAAK,QAA2D;AACpE,UAAM,QAAQ,IAAI,gBAAgB;AAClC,QAAI,QAAQ,UAAU,OAAW,OAAM,IAAI,SAAS,OAAO,OAAO,KAAK,CAAC;AACxE,QAAI,QAAQ,WAAW,OAAW,OAAM,IAAI,UAAU,OAAO,OAAO,MAAM,CAAC;AAC3E,QAAI,QAAQ,MAAO,OAAM,IAAI,SAAS,OAAO,KAAK;AAClD,UAAM,YAAY,QAAQ,aAAa,KAAK;AAC5C,QAAI,UAAW,OAAM,IAAI,iBAAiB,SAAS;AACnD,UAAM,KAAK,MAAM,SAAS;AAC1B,UAAM,OAAO,KAAK,gBAAgB,EAAE,KAAK;AACzC,UAAM,MAAM,MAAM,KAAK,KAAK,IAAwC,IAAI;AACxE,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,IAAI,IAA6B;AACrC,UAAM,MAAM,MAAM,KAAK,KAAK,IAAyB,gBAAgB,EAAE,EAAE;AACzE,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,MAAM,QAAkD;AAC5D,UAAM,YAAY,QAAQ,aAAa,KAAK;AAC5C,UAAM,OAAO,YACT,oCAAoC,mBAAmB,SAAS,CAAC,KACjE;AACJ,UAAM,MAAM,MAAM,KAAK,KAAK,IAAoC,IAAI;AACpE,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA,EAEA,MAAM,OAAO,UAAkB,OAA8B;AAC3D,UAAM,KAAK,KAAK,KAAK,gBAAgB,QAAQ,SAAS,EAAE,MAAM,CAAC;AAAA,EACjE;AAAA,EAEA,MAAM,UAAU,UAAkB,OAA8B;AAC9D,UAAM,KAAK,KAAK,OAAO,gBAAgB,QAAQ,SAAS,KAAK,EAAE;AAAA,EACjE;AAAA,EAEA,MAAM,YAAY,UAAkB,SAAiB,cAA2B,QAAwC;AACtH,UAAM,MAAM,MAAM,KAAK,KAAK,KAAyC,gBAAgB,QAAQ,aAAa;AAAA,MACxG;AAAA,MACA;AAAA,IACF,CAAC;AACD,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,YAAY,UAAkB,QAAkD;AACpF,UAAM,MAAM,MAAM,KAAK,KAAK,IAAyB,gBAAgB,QAAQ,aAAa,MAAM;AAChG,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,YAAY,UAAkB,YAAmC;AACrE,UAAM,KAAK,KAAK,KAAK,gBAAgB,QAAQ,cAAc,EAAE,WAAW,CAAC;AAAA,EAC3E;AAAA,EAEA,MAAM,eAAe,UAAiC;AACpD,UAAM,KAAK,KAAK,OAAO,gBAAgB,QAAQ,YAAY;AAAA,EAC7D;AACF;;;AC7DO,IAAM,eAAN,MAAmB;AAAA,EACxB,YAA6B,MAAkB;AAAlB;AAAA,EAAmB;AAAA,EAEhD,MAAM,OAAuB;AAC3B,UAAM,MAAM,MAAM,KAAK,KAAK,IAAwB,WAAW;AAC/D,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,OAAqC;AAChD,UAAM,MAAM,MAAM,KAAK,KAAK,KAAuB,aAAa,KAAK;AACrE,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,IAA2B;AACtC,UAAM,KAAK,KAAK,OAAO,aAAa,EAAE,EAAE;AAAA,EAC1C;AACF;;;ACLO,IAAM,oBAAN,MAAwB;AAAA,EAC7B,YACmB,MACA,kBACjB;AAFiB;AACA;AAAA,EAChB;AAAA,EAEH,MAAM,KAAK,QAA8D;AACvE,UAAM,YAAY,QAAQ,aAAa,KAAK;AAC5C,UAAM,QAAQ,YAAY,kBAAkB,SAAS,KAAK;AAC1D,UAAM,MAAM,MAAM,KAAK,KAAK,IAAqC,iBAAiB,KAAK,EAAE;AACzF,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,IAAI,IAAwC;AAChD,UAAM,MAAM,MAAM,KAAK,KAAK,IAAoC,kBAAkB,EAAE,EAAE;AACtF,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,OAA4E;AACvF,UAAM,OAAO,EAAE,GAAG,MAAM;AACxB,QAAI,CAAC,KAAK,iBAAiB,KAAK,kBAAkB;AAChD,WAAK,gBAAgB,KAAK;AAAA,IAC5B;AACA,UAAM,MAAM,MAAM,KAAK,KAAK,KAA4B,kBAAkB,IAAI;AAC9E,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,IAAY,OAA+C;AACtE,UAAM,MAAM,MAAM,KAAK,KAAK,IAA2B,kBAAkB,EAAE,IAAI,KAAK;AACpF,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,IAA2B;AACtC,UAAM,KAAK,KAAK,OAAO,kBAAkB,EAAE,EAAE;AAAA,EAC/C;AAAA,EAEA,MAAM,QAAQ,YAAoB,OAA+C;AAC/E,UAAM,MAAM,MAAM,KAAK,KAAK,KAAgC,kBAAkB,UAAU,UAAU,KAAK;AACvG,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,WAAW,YAAoB,QAAgB,OAA+C;AAClG,UAAM,MAAM,MAAM,KAAK,KAAK,IAA+B,kBAAkB,UAAU,UAAU,MAAM,IAAI,KAAK;AAChH,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,WAAW,YAAoB,QAA+B;AAClE,UAAM,KAAK,KAAK,OAAO,kBAAkB,UAAU,UAAU,MAAM,EAAE;AAAA,EACvE;AAAA,EAEA,MAAM,OAAO,YAAoB,UAAqD;AACpF,UAAM,MAAM,MAAM,KAAK,KAAK;AAAA,MAC1B,kBAAkB,UAAU,WAAW,QAAQ;AAAA,IACjD;AACA,WAAO,IAAI;AAAA,EACb;AACF;;;ACnEO,IAAM,qBAAN,MAAyB;AAAA,EAC9B,YACmB,MACA,kBACjB;AAFiB;AACA;AAAA,EAChB;AAAA,EAEH,MAAM,KAAK,QAAuD;AAChE,UAAM,YAAY,QAAQ,aAAa,KAAK;AAC5C,UAAM,QAAQ,YAAY,kBAAkB,SAAS,KAAK;AAC1D,UAAM,MAAM,MAAM,KAAK,KAAK,IAA8B,kBAAkB,KAAK,EAAE;AACnF,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,IAAI,IAAgC;AACxC,UAAM,MAAM,MAAM,KAAK,KAAK,IAA4B,mBAAmB,EAAE,EAAE;AAC/E,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,OAA8E;AACzF,UAAM,OAAO,EAAE,GAAG,MAAM;AACxB,QAAI,CAAC,KAAK,iBAAiB,KAAK,kBAAkB;AAChD,WAAK,gBAAgB,KAAK;AAAA,IAC5B;AACA,UAAM,MAAM,MAAM,KAAK,KAAK,KAA6B,mBAAmB,IAAI;AAChF,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,IAAY,OAAiD;AACxE,UAAM,MAAM,MAAM,KAAK,KAAK,IAA4B,mBAAmB,EAAE,IAAI,KAAK;AACtF,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,IAA2B;AACtC,UAAM,KAAK,KAAK,OAAO,mBAAmB,EAAE,EAAE;AAAA,EAChD;AAAA,EAEA,MAAM,KAAK,IAAgC;AACzC,UAAM,MAAM,MAAM,KAAK,KAAK,KAA6B,mBAAmB,EAAE,OAAO;AACrF,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,cAAc,IAAY,YAAkD;AAChF,UAAM,MAAM,MAAM,KAAK,KAAK;AAAA,MAC1B,mBAAmB,EAAE;AAAA,MACrB,EAAE,WAAW;AAAA,IACf;AACA,WAAO,IAAI;AAAA,EACb;AACF;;;AChDO,IAAM,oBAAN,MAAwB;AAAA,EAC7B,YAA6B,MAAkB;AAAlB;AAAA,EAAmB;AAAA,EAEhD,MAAM,OAA4B;AAChC,UAAM,MAAM,MAAM,KAAK,KAAK,IAA6B,iBAAiB;AAC1E,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,MAA4D;AACvE,UAAM,MAAM,MAAM,KAAK,KAAK,KAA0C,mBAAmB,IAAI;AAC7F,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,YAAmC;AAC9C,UAAM,KAAK,KAAK,OAAO,mBAAmB,mBAAmB,UAAU,CAAC,EAAE;AAAA,EAC5E;AAAA,EAEA,MAAM,WAAW,YAAmC;AAClD,UAAM,KAAK,KAAK,KAAK,mBAAmB,mBAAmB,UAAU,CAAC,UAAU;AAAA,EAClF;AACF;;;ACpBO,IAAM,uBAAN,MAA2B;AAAA,EAChC,YAA6B,MAAkB;AAAlB;AAAA,EAAmB;AAAA,EAEhD,MAAM,OAA+B;AACnC,UAAM,MAAM,MAAM,KAAK,KAAK,IAAgC,oBAAoB;AAChF,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,OAAqD;AAChE,UAAM,MAAM,MAAM,KAAK,KAAK,KAA+B,sBAAsB,KAAK;AACtF,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,IAAI,IAA4C;AACpD,UAAM,MAAM,MAAM,KAAK,KAAK,IAAwC,sBAAsB,EAAE,EAAE;AAC9F,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,IAA2B;AACtC,UAAM,KAAK,KAAK,OAAO,sBAAsB,EAAE,EAAE;AAAA,EACnD;AACF;;;ACfO,IAAM,gBAAN,MAAoB;AAAA,EACzB,YAA6B,MAAkB;AAAlB;AAAA,EAAmB;AAAA,EAEhD,MAAM,OAAwB;AAC5B,UAAM,MAAM,MAAM,KAAK,KAAK,IAAyB,YAAY;AACjE,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,IAAI,IAA2B;AACnC,UAAM,MAAM,MAAM,KAAK,KAAK,IAAuB,cAAc,EAAE,EAAE;AACrE,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,OAAuC;AAClD,UAAM,MAAM,MAAM,KAAK,KAAK,KAAwB,cAAc,KAAK;AACvE,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,IAAY,OAAuC;AAC9D,UAAM,MAAM,MAAM,KAAK,KAAK,IAAuB,cAAc,EAAE,IAAI,KAAK;AAC5E,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,OAAO,IAA2B;AACtC,UAAM,KAAK,KAAK,OAAO,cAAc,EAAE,EAAE;AAAA,EAC3C;AAAA,EAEA,MAAM,eAAe,QAA2C;AAC9D,UAAM,MAAM,MAAM,KAAK,KAAK;AAAA,MAC1B,cAAc,MAAM;AAAA,IACtB;AACA,WAAO,IAAI;AAAA,EACb;AACF;;;AC1CA,IAAM,cAAsC;AAAA,EAC1C,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAEO,SAAS,WAAW,OAAuB;AAChD,QAAM,QAAQ,MAAM,MAAM,iBAAiB;AAC3C,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,0BAA0B,KAAK,6CAA6C;AAAA,EAC9F;AACA,SAAO,OAAO,MAAM,CAAC,CAAC,IAAI,YAAY,MAAM,CAAC,CAAC;AAChD;;;ACPO,IAAM,YAAN,MAAgB;AAAA,EACrB,YACmB,SACA,WACA,YACjB;AAHiB;AACA;AACA;AAAA,EAChB;AAAA,EAEH,MAAM,mBACJ,MACA,aACA,OAC4B;AAC5B,UAAM,WAAW,MAAM,KAAK,UAAU,OAAO,EAAE,MAAM,YAAY,CAAC;AAElE,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,OAAO,MAAM,CAAC;AACpB,YAAM,KAAK,UAAU,QAAQ,SAAS,IAAI;AAAA,QACxC,WAAW,IAAI;AAAA,QACf,cAAc,WAAW,KAAK,KAAK;AAAA,QACnC,aAAa,KAAK;AAAA,QAClB,gBAAgB,KAAK;AAAA,MACvB,CAAC;AAAA,IACH;AAEA,WAAO,KAAK,UAAU,IAAI,SAAS,EAAE;AAAA,EACvC;AAAA,EAEA,MAAM,cAAc,MAAkC;AACpD,UAAM,YAAY,MAAM,KAAK,WAAW,OAAO;AAAA,MAC7C,OAAO,KAAK,MAAM,GAAG,EAAE;AAAA,MACvB,aAAa;AAAA,MACb,gBAAgB;AAAA,MAChB,YAAY;AAAA,IACd,CAAC;AACD,WAAO,KAAK,WAAW,KAAK,UAAU,EAAE;AAAA,EAC1C;AAAA,EAEA,MAAM,eACJ,OACA,aACA,SACoB;AACpB,UAAM,YAAY,MAAM,KAAK,WAAW,OAAO;AAAA,MAC7C,OAAO,QAAQ,MAAM,GAAG,EAAE;AAAA,MAC1B;AAAA,MACA,gBAAgB;AAAA,MAChB,YAAY;AAAA,MACZ,aAAa;AAAA,IACf,CAAC;AACD,WAAO,KAAK,WAAW,KAAK,UAAU,EAAE;AAAA,EAC1C;AAAA,EAEA,MAAM,mBACJ,aACA,SACA,YACoB;AACpB,UAAM,YAAY,MAAM,KAAK,WAAW,OAAO;AAAA,MAC7C,OAAO,QAAQ,MAAM,GAAG,EAAE;AAAA,MAC1B;AAAA,MACA,gBAAgB;AAAA,MAChB,YAAY;AAAA,IACd,CAAC;AACD,WAAO,KAAK,WAAW,cAAc,UAAU,IAAI,UAAU;AAAA,EAC/D;AAAA,EAEA,MAAM,iBAAiB,UAAkB,MAA8C;AACrF,WAAO,KAAK,QAAQ,YAAY,UAAU,MAAM,MAAM;AAAA,EACxD;AAAA,EAEA,MAAM,iBAAiB,UAAkB,UAAkD;AACzF,WAAO,KAAK,QAAQ,YAAY,UAAU,UAAU,MAAM;AAAA,EAC5D;AACF;;;ACpEO,IAAM,cAAN,MAAkB;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEQ;AAAA,EACA;AAAA,EACA;AAAA,EAER;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,QAA2B;AACrC,SAAK,SAAS,OAAO,OAAO,QAAQ,OAAO,EAAE;AAC7C,SAAK,mBAAmB,OAAO;AAE/B,UAAM,OAAO,IAAI,WAAW;AAAA,MAC1B,SAAS,KAAK;AAAA,MACd,QAAQ,OAAO;AAAA,MACf,SAAS,OAAO,WAAW;AAAA,IAC7B,CAAC;AAED,SAAK,UAAU,IAAI,gBAAgB,MAAM,KAAK,gBAAgB;AAC9D,SAAK,OAAO,IAAI,aAAa,IAAI;AACjC,SAAK,YAAY,IAAI,kBAAkB,MAAM,KAAK,gBAAgB;AAClE,SAAK,aAAa,IAAI,mBAAmB,MAAM,KAAK,gBAAgB;AACpE,SAAK,YAAY,IAAI,kBAAkB,IAAI;AAC3C,SAAK,eAAe,IAAI,qBAAqB,IAAI;AACjD,SAAK,QAAQ,IAAI,cAAc,IAAI;AACnC,SAAK,YAAY,IAAI,UAAU,KAAK,SAAS,KAAK,WAAW,KAAK,UAAU;AAE5E,SAAK,qBAAqB,KAAK,UAAU,mBAAmB,KAAK,KAAK,SAAS;AAC/E,SAAK,gBAAgB,KAAK,UAAU,cAAc,KAAK,KAAK,SAAS;AACrE,SAAK,iBAAiB,KAAK,UAAU,eAAe,KAAK,KAAK,SAAS;AACvE,SAAK,qBAAqB,KAAK,UAAU,mBAAmB,KAAK,KAAK,SAAS;AAC/E,SAAK,mBAAmB,KAAK,UAAU,iBAAiB,KAAK,KAAK,SAAS;AAC3E,SAAK,mBAAmB,KAAK,UAAU,iBAAiB,KAAK,KAAK,SAAS;AAAA,EAC7E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,WAAW,SAAuD;AAChE,UAAM,MAAM,IAAI,IAAI,GAAG,KAAK,MAAM,YAAY;AAC9C,QAAI,SAAS,IAAK,KAAI,aAAa,IAAI,OAAO,QAAQ,GAAG;AACzD,QAAI,SAAS,SAAU,KAAI,aAAa,IAAI,YAAY,QAAQ,QAAQ;AACxE,WAAO,IAAI,SAAS;AAAA,EACtB;AACF;","names":[]}
|