koishi-plugin-booth-get 3.1.0 → 5.0.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/lib/index.js +345 -189
- package/package.json +4 -4
package/lib/index.js
CHANGED
|
@@ -26,215 +26,371 @@ __export(src_exports, {
|
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(src_exports);
|
|
28
28
|
var import_koishi = require("koishi");
|
|
29
|
-
var
|
|
29
|
+
var QRCode = require("qrcode");
|
|
30
|
+
|
|
30
31
|
var name = "booth-get";
|
|
31
32
|
var inject = ["puppeteer"];
|
|
32
33
|
var Config = import_koishi.Schema.object({
|
|
33
|
-
loadTimeout: import_koishi.Schema.natural().role("ms").description("
|
|
34
|
-
idleTimeout: import_koishi.Schema.natural().role("ms").description("
|
|
35
|
-
proxyServer: import_koishi.Schema.string().description("
|
|
36
|
-
wsServer: import_koishi.Schema.string().description("WebSocket服务器地址。").default(""),
|
|
37
|
-
enableWs: import_koishi.Schema.boolean().description("是否启用WebSocket连接。").default(false),
|
|
34
|
+
loadTimeout: import_koishi.Schema.natural().role("ms").description("加载页面的最长时间").default(import_koishi.Time.second * 5),
|
|
35
|
+
idleTimeout: import_koishi.Schema.natural().role("ms").description("等待页面空闲的最长时间").default(import_koishi.Time.second * 30),
|
|
36
|
+
proxyServer: import_koishi.Schema.string().description("代理服务器地址").default(""),
|
|
38
37
|
}).description("booth-get");
|
|
39
|
-
function apply(ctx, config) {
|
|
40
|
-
const logger = ctx.logger("screenshot");
|
|
41
|
-
const { defaultViewport } = ctx.puppeteer.config;
|
|
42
|
-
const { loadTimeout, idleTimeout,proxyServer } = config;
|
|
43
|
-
const booth_url = "https://booth.pm/zh-cn/items/";
|
|
44
|
-
const maxSize = 1024 * 1024;
|
|
45
|
-
const launchOptions = { ...ctx.puppeteer.config.launchOptions };
|
|
46
|
-
|
|
47
|
-
if (config.enableWs && config.wsServer) {
|
|
48
|
-
const WebSocket = require('ws');
|
|
49
|
-
let wsUrl = config.wsServer;
|
|
50
|
-
if (!wsUrl.startsWith('ws://') && !wsUrl.startsWith('wss://')) {
|
|
51
|
-
wsUrl = 'ws://' + wsUrl;
|
|
52
|
-
}
|
|
53
|
-
const ws = new WebSocket(wsUrl);
|
|
54
|
-
ws.on('open', function open() {
|
|
55
|
-
console.log('Connected to WebSocket server');
|
|
56
|
-
ws.send('Hello, server!');
|
|
57
|
-
});
|
|
58
|
-
ws.on('message', function incoming(data) {
|
|
59
|
-
console.log('Received message:', data);
|
|
60
|
-
});
|
|
61
|
-
ws.on('error', function handleErrors(error) {
|
|
62
|
-
console.error('WebSocket error:', error);
|
|
63
|
-
});
|
|
64
|
-
ws.on('close', function close() {
|
|
65
|
-
console.log('Disconnected from WebSocket server');
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (proxyServer) {
|
|
70
|
-
launchOptions.args = launchOptions.args || [];
|
|
71
|
-
launchOptions.args.push(`--proxy-server=${proxyServer}`);
|
|
72
|
-
}
|
|
73
|
-
ctx.command("摊位 <id>").action(async ({ session }, id) => {
|
|
74
|
-
if (!id)
|
|
75
|
-
return "请输入ID";
|
|
76
|
-
let url = booth_url + id;
|
|
77
|
-
let selector = "div.u-pt-600";
|
|
78
|
-
const result = ctx.bail("screenshot/validate", url);
|
|
79
|
-
if (typeof result === "string")
|
|
80
|
-
return result;
|
|
81
|
-
let loaded = false;
|
|
82
|
-
const page = await ctx.puppeteer.page(launchOptions);
|
|
83
|
-
page.on("load", () => loaded = true);
|
|
84
|
-
try {
|
|
85
|
-
await new Promise((resolve, reject) => {
|
|
86
|
-
logger.debug(`navigating to ${url}`);
|
|
87
|
-
const _resolve = /* @__PURE__ */ __name(() => {
|
|
88
|
-
clearTimeout(timer);
|
|
89
|
-
resolve();
|
|
90
|
-
}, "_resolve");
|
|
91
|
-
page.goto(url, {
|
|
92
|
-
waitUntil: "networkidle0",
|
|
93
|
-
timeout: idleTimeout
|
|
94
|
-
}).then(_resolve, () => {
|
|
95
|
-
return loaded ? _resolve() : reject(new Error("navigation timeout"));
|
|
96
|
-
});
|
|
97
|
-
const timer = setTimeout(() => {
|
|
98
|
-
return loaded ? session.send("正在加载中,请稍等片刻~") : reject(new Error("navigation timeout"));
|
|
99
|
-
}, loadTimeout);
|
|
100
|
-
});
|
|
101
|
-
} catch (error) {
|
|
102
|
-
page.close();
|
|
103
|
-
logger.debug(error);
|
|
104
|
-
return "无法打开页面。";
|
|
105
|
-
}
|
|
106
|
-
const shooter = selector ? await page.$(selector) : page;
|
|
107
|
-
if (!shooter)
|
|
108
|
-
return "找不到满足该选择器的元素。";
|
|
109
|
-
return shooter.screenshot().then(async (buffer) => {
|
|
110
|
-
if (buffer.byteLength > maxSize) {
|
|
111
|
-
await new Promise((resolve, reject) => {
|
|
112
|
-
const png = new import_pngjs.PNG();
|
|
113
|
-
png.parse(buffer, (error, data) => {
|
|
114
|
-
return error ? reject(error) : resolve(data);
|
|
115
|
-
});
|
|
116
|
-
}).then((data) => {
|
|
117
|
-
const width = data.width;
|
|
118
|
-
const height = data.height * maxSize / buffer.byteLength;
|
|
119
|
-
const png = new import_pngjs.PNG({ width, height });
|
|
120
|
-
data.bitblt(png, 0, 0, width, height, 0, 0);
|
|
121
|
-
buffer = import_pngjs.PNG.sync.write(png);
|
|
122
|
-
}).catch(import_koishi.noop);
|
|
123
|
-
}
|
|
124
|
-
return import_koishi.h.image(buffer, "image/png");
|
|
125
|
-
}, (error) => {
|
|
126
|
-
logger.debug(error);
|
|
127
|
-
return "截图失败。";
|
|
128
|
-
}).finally(() => page.close());
|
|
129
|
-
});
|
|
130
38
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
39
|
+
async function getBoothItem(id) {
|
|
40
|
+
try {
|
|
41
|
+
const [itemRes, wishRes] = await Promise.all([
|
|
42
|
+
fetch(`https://booth.pm/zh-cn/items/${id}.json`),
|
|
43
|
+
fetch(`https://accounts.booth.pm/wish_lists.json?item_ids%5B%5D=${id}`)
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
const itemData = await itemRes.json();
|
|
47
|
+
const wishData = await wishRes.json();
|
|
134
48
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
49
|
+
return {
|
|
50
|
+
id,
|
|
51
|
+
title: itemData.name,
|
|
52
|
+
price: itemData.price,
|
|
53
|
+
image_url: itemData.images[0]?.original,
|
|
54
|
+
description: itemData.description,
|
|
55
|
+
category: itemData.category?.name,
|
|
56
|
+
parent_category: itemData.category?.parent?.name,
|
|
57
|
+
author: itemData.shop?.name,
|
|
58
|
+
likes: wishData.wishlists_counts[id] || 0,
|
|
59
|
+
tags: itemData.tags
|
|
60
|
+
};
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function fetchRelatedItems(author) {
|
|
66
|
+
try {
|
|
67
|
+
const res = await fetch(`https://booth.pm/zh-cn/search.json?q=${encodeURIComponent(author)}&in_stock=true`);
|
|
68
|
+
const data = await res.json();
|
|
69
|
+
return data.items
|
|
70
|
+
.filter(i => i.shop?.name === author)
|
|
71
|
+
.slice(0, 3)
|
|
72
|
+
.map(item => ({
|
|
73
|
+
id: item.id,
|
|
74
|
+
title: item.name,
|
|
75
|
+
price: item.price,
|
|
76
|
+
image_url: item.images[0]?.original,
|
|
77
|
+
}));
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
139
82
|
|
|
140
|
-
|
|
83
|
+
async function generateQRCode(id) {
|
|
84
|
+
return QRCode.toDataURL(`https://booth.pm/zh-cn/items/${id}`);
|
|
85
|
+
}
|
|
141
86
|
|
|
142
|
-
|
|
87
|
+
function generateCardHTML(item, relatedItems = []) {
|
|
88
|
+
return `
|
|
89
|
+
<html>
|
|
90
|
+
<head>
|
|
91
|
+
<meta charset="utf-8">
|
|
92
|
+
<style>
|
|
93
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');
|
|
94
|
+
body {
|
|
95
|
+
margin: 0;
|
|
96
|
+
padding: 0;
|
|
97
|
+
font-family: 'Inter', sans-serif;
|
|
98
|
+
background: #f8f8f8;
|
|
99
|
+
display: flex;
|
|
100
|
+
justify-content: center;
|
|
101
|
+
align-items: center;
|
|
102
|
+
min-height: 100vh;
|
|
103
|
+
}
|
|
104
|
+
.container {
|
|
105
|
+
width: 640px;
|
|
106
|
+
background: white;
|
|
107
|
+
border-radius: 12px;
|
|
108
|
+
overflow: hidden;
|
|
109
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
|
110
|
+
}
|
|
111
|
+
.header {
|
|
112
|
+
padding: 18px 25px;
|
|
113
|
+
border-bottom: 1px solid #eee;
|
|
114
|
+
position: relative;
|
|
115
|
+
}
|
|
116
|
+
.header::after {
|
|
117
|
+
content: "BOOTH";
|
|
118
|
+
position: absolute;
|
|
119
|
+
right: 15px;
|
|
120
|
+
top: 50%;
|
|
121
|
+
transform: translateY(-50%) rotate(-90deg);
|
|
122
|
+
font-size: 56px;
|
|
123
|
+
color: #999;
|
|
124
|
+
opacity: 0.2;
|
|
125
|
+
font-weight: 800;
|
|
126
|
+
}
|
|
127
|
+
.label {
|
|
128
|
+
position: absolute;
|
|
129
|
+
top: 15px;
|
|
130
|
+
left: 15px;
|
|
131
|
+
background: rgba(255,255,255,0.95);
|
|
132
|
+
padding: 6px 12px;
|
|
133
|
+
border-radius: 6px;
|
|
134
|
+
font-size: 12px;
|
|
135
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
136
|
+
}
|
|
137
|
+
.content {
|
|
138
|
+
padding: 25px;
|
|
139
|
+
}
|
|
140
|
+
.main-image {
|
|
141
|
+
width: 100%;
|
|
142
|
+
height: 320px;
|
|
143
|
+
background: #f0f0f0 url('${item.image_url}') center/cover;
|
|
144
|
+
border-radius: 8px;
|
|
145
|
+
margin-bottom: 20px;
|
|
146
|
+
}
|
|
147
|
+
.product-title {
|
|
148
|
+
font-size: 24px;
|
|
149
|
+
margin: 0 0 15px 0;
|
|
150
|
+
color: #333;
|
|
151
|
+
}
|
|
152
|
+
.author-section {
|
|
153
|
+
display: flex;
|
|
154
|
+
align-items: center;
|
|
155
|
+
gap: 12px;
|
|
156
|
+
margin-bottom: 20px;
|
|
157
|
+
}
|
|
158
|
+
.author-avatar {
|
|
159
|
+
width: 40px;
|
|
160
|
+
height: 40px;
|
|
161
|
+
border-radius: 50%;
|
|
162
|
+
border: 2px solid #eee;
|
|
163
|
+
}
|
|
164
|
+
.price-section {
|
|
165
|
+
font-size: 28px;
|
|
166
|
+
color: #e74c3c;
|
|
167
|
+
margin-bottom: 25px;
|
|
168
|
+
}
|
|
169
|
+
.description {
|
|
170
|
+
color: #666;
|
|
171
|
+
line-height: 1.6;
|
|
172
|
+
padding-bottom: 20px;
|
|
173
|
+
border-bottom: 1px solid #eee;
|
|
174
|
+
}
|
|
175
|
+
.related-works {
|
|
176
|
+
margin-top: 25px;
|
|
177
|
+
}
|
|
178
|
+
.related-title {
|
|
179
|
+
font-size: 18px;
|
|
180
|
+
color: #444;
|
|
181
|
+
margin-bottom: 15px;
|
|
182
|
+
}
|
|
183
|
+
.works-grid {
|
|
184
|
+
display: grid;
|
|
185
|
+
grid-template-columns: repeat(3, 1fr);
|
|
186
|
+
gap: 15px;
|
|
187
|
+
}
|
|
188
|
+
.work-item {
|
|
189
|
+
background: #f8f8f8;
|
|
190
|
+
border-radius: 8px;
|
|
191
|
+
overflow: hidden;
|
|
192
|
+
transition: transform 0.2s;
|
|
193
|
+
}
|
|
194
|
+
.work-item:hover {
|
|
195
|
+
transform: translateY(-3px);
|
|
196
|
+
}
|
|
197
|
+
.work-image {
|
|
198
|
+
height: 120px;
|
|
199
|
+
background-size: cover;
|
|
200
|
+
background-position: center;
|
|
201
|
+
}
|
|
202
|
+
.work-info {
|
|
203
|
+
padding: 10px;
|
|
204
|
+
}
|
|
205
|
+
.work-price {
|
|
206
|
+
font-size: 14px;
|
|
207
|
+
color: #e74c3c;
|
|
208
|
+
}
|
|
209
|
+
.footer {
|
|
210
|
+
background: #f8f8f8;
|
|
211
|
+
padding: 15px;
|
|
212
|
+
text-align: center;
|
|
213
|
+
color: #666;
|
|
214
|
+
font-size: 13px;
|
|
215
|
+
border-top: 1px solid #eee;
|
|
216
|
+
}
|
|
217
|
+
</style>
|
|
218
|
+
</head>
|
|
219
|
+
<body>
|
|
220
|
+
<div class="container">
|
|
221
|
+
<div class="header">
|
|
222
|
+
<div class="label">NEW ARRIVAL</div>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
<div class="content">
|
|
226
|
+
<div class="main-image"></div>
|
|
227
|
+
<h1 class="product-title">${item.title}</h1>
|
|
228
|
+
|
|
229
|
+
<div class="author-section">
|
|
230
|
+
<img src="${item.author_thumbnail_url || 'https://s2.booth.pm/static-images/user/guest-32.png'}"
|
|
231
|
+
class="author-avatar"
|
|
232
|
+
alt="作者头像">
|
|
233
|
+
<div>
|
|
234
|
+
<div style="font-weight:600;">${item.author}</div>
|
|
235
|
+
<div style="font-size:13px;color:#666;">BOOTHクリエイター</div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
143
238
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const match = content.match(regex);
|
|
239
|
+
<div class="price-section">¥${item.price.toLocaleString()}</div>
|
|
240
|
+
|
|
241
|
+
<div class="description">
|
|
242
|
+
<p>${item.description.slice(0, 200)}${item.description.length > 200 ? '...' : ''}</p>
|
|
243
|
+
</div>
|
|
150
244
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
245
|
+
${relatedItems.length > 0 ? `
|
|
246
|
+
<div class="related-works">
|
|
247
|
+
<h3 class="related-title">同じ作者の作品</h3>
|
|
248
|
+
<div class="works-grid">
|
|
249
|
+
${relatedItems.map(work => `
|
|
250
|
+
<div class="work-item">
|
|
251
|
+
<div class="work-image" style="background-image:url('${work.image_url}')"></div>
|
|
252
|
+
<div class="work-info">
|
|
253
|
+
<div style="font-size:13px;margin-bottom:6px;">${work.title.slice(0, 18)}${work.title.length > 18 ? '...' : ''}</div>
|
|
254
|
+
<div class="work-price">¥${work.price.toLocaleString()}</div>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
`).join('')}
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
` : ''}
|
|
261
|
+
</div>
|
|
154
262
|
|
|
155
|
-
|
|
156
|
-
|
|
263
|
+
<div class="footer">
|
|
264
|
+
由VRCBBS提供 | BOOTHリンク:
|
|
265
|
+
<a href="https://booth.pm/zh-cn/items/${item.id}"
|
|
266
|
+
style="color:#3498db;text-decoration:none;">
|
|
267
|
+
https://booth.pm/items/${item.id}
|
|
268
|
+
</a>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
</body>
|
|
272
|
+
</html>`;
|
|
273
|
+
}
|
|
274
|
+
async function captureCard(ctx, id) {
|
|
275
|
+
const logger = ctx.logger("booth-get");
|
|
276
|
+
const item = await getBoothItem(id);
|
|
277
|
+
if (!item) return null;
|
|
157
278
|
|
|
158
|
-
|
|
279
|
+
const relatedItems = await fetchRelatedItems(item.author);
|
|
280
|
+
|
|
281
|
+
const html = generateCardHTML(item, relatedItems);
|
|
159
282
|
|
|
160
|
-
const shooter = await page.$('div.u-pt-600');
|
|
161
|
-
if (!shooter) {
|
|
162
|
-
return "找不到满足该选择器的元素。";
|
|
163
|
-
}
|
|
164
|
-
const buffer = await shooter.screenshot();
|
|
165
|
-
if (buffer.byteLength > maxSize) {
|
|
166
|
-
}
|
|
167
|
-
return import_koishi.h.image(buffer, "image/png");
|
|
168
|
-
} catch (error) {
|
|
169
|
-
logger.debug(error);
|
|
170
|
-
return "操作失败。";
|
|
171
|
-
} finally {
|
|
172
|
-
await page.close();
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
async function captureBoothPage(url) {
|
|
176
283
|
const page = await ctx.puppeteer.page();
|
|
177
284
|
try {
|
|
178
|
-
await page.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
285
|
+
await page.setRequestInterception(true);
|
|
286
|
+
page.on('request', (request) => request.continue());
|
|
287
|
+
|
|
288
|
+
await page.setContent(html, {
|
|
289
|
+
waitUntil: 'networkidle0',
|
|
290
|
+
timeout: ctx.config.loadTimeout
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
await page.setViewport({ width: 640, height: 1200 });
|
|
294
|
+
const container = await page.$('.container');
|
|
295
|
+
return await container.screenshot({
|
|
296
|
+
type: 'png',
|
|
297
|
+
encoding: 'binary',
|
|
298
|
+
captureBeyondViewport: false
|
|
299
|
+
});
|
|
185
300
|
} catch (error) {
|
|
186
|
-
|
|
301
|
+
logger.error('生成失败:', error);
|
|
302
|
+
return null;
|
|
187
303
|
} finally {
|
|
188
304
|
await page.close();
|
|
189
305
|
}
|
|
190
306
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
307
|
+
|
|
308
|
+
function apply(ctx, config) {
|
|
309
|
+
const logger = ctx.logger("booth-get");
|
|
310
|
+
|
|
311
|
+
ctx.command("摊位 <id>")
|
|
312
|
+
.action(async ({ session }, id) => {
|
|
313
|
+
if (!id) return "请输入商品ID";
|
|
314
|
+
try {
|
|
315
|
+
const buffer = await captureCard(ctx, id);
|
|
316
|
+
return buffer ? import_koishi.h.image(buffer, "image/png") : "商品获取失败";
|
|
317
|
+
} catch (error) {
|
|
318
|
+
logger.warn(error);
|
|
319
|
+
return "卡片生成失败";
|
|
320
|
+
}
|
|
202
321
|
});
|
|
203
|
-
}
|
|
204
322
|
|
|
205
|
-
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
323
|
+
ctx.command("摊位名称 <query>")
|
|
324
|
+
.action(async ({ session }, query) => {
|
|
325
|
+
if (!query) return "请输入搜索关键词";
|
|
326
|
+
|
|
327
|
+
const tags = ['3Dモデル', 'Vrchat'];
|
|
328
|
+
const tagsParams = tags.map(tag => `tags[]=${encodeURIComponent(tag)}`).join('&');
|
|
329
|
+
const searchUrl = `https://booth.pm/zh-cn/search/${encodeURIComponent(query)}?${tagsParams}&min_price=4500`;
|
|
330
|
+
|
|
331
|
+
const page = await ctx.puppeteer.page();
|
|
332
|
+
try {
|
|
333
|
+
let retries = 3;
|
|
334
|
+
while (retries > 0) {
|
|
335
|
+
try {
|
|
336
|
+
await page.goto(searchUrl, { waitUntil: 'networkidle0', timeout: 10000 });
|
|
337
|
+
break;
|
|
338
|
+
} catch (error) {
|
|
339
|
+
retries--;
|
|
340
|
+
if (retries === 0) throw error;
|
|
341
|
+
logger.warn(`页面加载失败,重试中... (剩余重试次数: ${retries})`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const pageTitle = await page.title();
|
|
345
|
+
logger.debug('页面标题:', pageTitle);
|
|
346
|
+
|
|
347
|
+
const content = await page.content();
|
|
348
|
+
logger.debug('页面内容:', content);
|
|
349
|
+
const regex = /item-card__wrap" id="item_(\d+)"/;
|
|
350
|
+
const match = content.match(regex);
|
|
351
|
+
|
|
352
|
+
if (!match) {
|
|
353
|
+
logger.debug('未找到商品 ID,页面内容:', content);
|
|
354
|
+
return "没有找到相关商品";
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const itemId = match[1];
|
|
358
|
+
logger.debug('找到商品 ID:', itemId);
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
logger.debug('正在生成卡片...');
|
|
362
|
+
const buffer = await captureCard(ctx, itemId);
|
|
363
|
+
if (!buffer) {
|
|
364
|
+
logger.debug('卡片生成失败');
|
|
365
|
+
return "卡片生成失败";
|
|
366
|
+
}
|
|
367
|
+
return import_koishi.h.image(buffer, "image/png");
|
|
368
|
+
} catch (error) {
|
|
369
|
+
logger.error('卡片生成失败:', error);
|
|
370
|
+
return "卡片生成失败";
|
|
371
|
+
}
|
|
372
|
+
} catch (error) {
|
|
373
|
+
logger.error('搜索失败:', error);
|
|
374
|
+
return "搜索失败";
|
|
375
|
+
} finally {
|
|
376
|
+
await page.close();
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
ctx.middleware(async (session, next) => {
|
|
381
|
+
const boothUrlRegex = /https:\/\/booth.pm\/[\w-]+\/items\/(\d+)/;
|
|
382
|
+
const match = session.content.match(boothUrlRegex);
|
|
383
|
+
|
|
384
|
+
if (match) {
|
|
385
|
+
try {
|
|
386
|
+
const buffer = await captureCard(ctx, match[1]);
|
|
387
|
+
return buffer ? session.send(import_koishi.h.image(buffer, "image/png")) : "商品解析失败";
|
|
388
|
+
} catch (error) {
|
|
389
|
+
logger.warn(error);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return next();
|
|
393
|
+
});
|
|
224
394
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
Config,
|
|
228
|
-
apply,
|
|
229
|
-
inject,
|
|
230
|
-
name
|
|
231
|
-
});
|
|
232
|
-
async function triggerImageGeneration() {
|
|
233
|
-
try {
|
|
234
|
-
const response = await fetch('http://localhost:8000/generate');
|
|
235
|
-
const data = await response.json();
|
|
236
|
-
console.log(data.message); // Log the response from the server
|
|
237
|
-
} catch (error) {
|
|
238
|
-
console.error('Error calling the API:', error);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
395
|
+
|
|
396
|
+
__name(apply, "apply");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-booth-get",
|
|
3
3
|
"description": "通过url与名称检查摊位物品并反馈用户搜索的图片",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "5.0.0",
|
|
5
5
|
"contributors": [
|
|
6
6
|
"rixiang <1148147857@qq.com>"
|
|
7
7
|
],
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"booth"
|
|
30
30
|
],
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"koishi": "4.18.
|
|
32
|
+
"koishi": "4.18.7"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"atsc": "^2.1.0",
|
|
36
|
-
"koishi-plugin-puppeteer": "^3.
|
|
36
|
+
"koishi-plugin-puppeteer": "^3.9.0",
|
|
37
37
|
"pngjs": "^7.0.0",
|
|
38
38
|
"puppeteer-core": "^22.12.1"
|
|
39
39
|
}
|
|
40
|
-
}
|
|
40
|
+
}
|