koishi-plugin-booth-get 5.0.0 → 5.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/lib/index.js +292 -161
- package/package.json +7 -5
- package/README.md +0 -26
package/lib/index.js
CHANGED
|
@@ -34,55 +34,9 @@ var Config = import_koishi.Schema.object({
|
|
|
34
34
|
loadTimeout: import_koishi.Schema.natural().role("ms").description("加载页面的最长时间").default(import_koishi.Time.second * 5),
|
|
35
35
|
idleTimeout: import_koishi.Schema.natural().role("ms").description("等待页面空闲的最长时间").default(import_koishi.Time.second * 30),
|
|
36
36
|
proxyServer: import_koishi.Schema.string().description("代理服务器地址").default(""),
|
|
37
|
-
|
|
38
|
-
|
|
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();
|
|
48
|
-
|
|
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
|
-
}
|
|
37
|
+
Text: import_koishi.Schema.title("摊位卡片生成器").description("生成BOOTH商品的摊位卡片,由VRCBBS提供"),
|
|
82
38
|
|
|
83
|
-
|
|
84
|
-
return QRCode.toDataURL(`https://booth.pm/zh-cn/items/${id}`);
|
|
85
|
-
}
|
|
39
|
+
}).description("booth-get");
|
|
86
40
|
|
|
87
41
|
function generateCardHTML(item, relatedItems = []) {
|
|
88
42
|
return `
|
|
@@ -90,12 +44,12 @@ function generateCardHTML(item, relatedItems = []) {
|
|
|
90
44
|
<head>
|
|
91
45
|
<meta charset="utf-8">
|
|
92
46
|
<style>
|
|
93
|
-
@import url('https://fonts.googleapis.com/css2?family=
|
|
47
|
+
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&family=Montserrat:wght@600;700;800&display=swap');
|
|
94
48
|
body {
|
|
95
49
|
margin: 0;
|
|
96
50
|
padding: 0;
|
|
97
|
-
font-family: '
|
|
98
|
-
background: #
|
|
51
|
+
font-family: 'Noto Sans SC', sans-serif;
|
|
52
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
99
53
|
display: flex;
|
|
100
54
|
justify-content: center;
|
|
101
55
|
align-items: center;
|
|
@@ -104,81 +58,146 @@ function generateCardHTML(item, relatedItems = []) {
|
|
|
104
58
|
.container {
|
|
105
59
|
width: 640px;
|
|
106
60
|
background: white;
|
|
107
|
-
border-radius:
|
|
61
|
+
border-radius: 20px;
|
|
108
62
|
overflow: hidden;
|
|
109
|
-
box-shadow: 0
|
|
63
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
64
|
+
position: relative;
|
|
110
65
|
}
|
|
111
66
|
.header {
|
|
112
|
-
|
|
113
|
-
|
|
67
|
+
background: linear-gradient(90deg, #ff6b6b, #ffa502);
|
|
68
|
+
padding: 25px;
|
|
69
|
+
text-align: center;
|
|
114
70
|
position: relative;
|
|
71
|
+
color: white;
|
|
115
72
|
}
|
|
116
|
-
.header::
|
|
117
|
-
content: "
|
|
73
|
+
.header::before {
|
|
74
|
+
content: "";
|
|
118
75
|
position: absolute;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
font-weight: 800;
|
|
76
|
+
top: 0;
|
|
77
|
+
left: 0;
|
|
78
|
+
right: 0;
|
|
79
|
+
bottom: 0;
|
|
80
|
+
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="none"><polygon points="0,0 100,100 0,100" fill="rgba(255,255,255,0.1)"/></svg>');
|
|
81
|
+
background-size: 100px 100px;
|
|
126
82
|
}
|
|
127
83
|
.label {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
84
|
+
background: rgba(255, 255, 255, 0.2);
|
|
85
|
+
backdrop-filter: blur(10px);
|
|
86
|
+
padding: 8px 20px;
|
|
87
|
+
border-radius: 30px;
|
|
88
|
+
font-size: 14px;
|
|
89
|
+
font-weight: 500;
|
|
90
|
+
display: inline-block;
|
|
91
|
+
margin-bottom: 15px;
|
|
92
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
93
|
+
}
|
|
94
|
+
.booth-logo {
|
|
95
|
+
font-family: 'Montserrat', sans-serif;
|
|
96
|
+
font-weight: 800;
|
|
97
|
+
font-size: 36px;
|
|
98
|
+
letter-spacing: 2px;
|
|
99
|
+
text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
|
|
136
100
|
}
|
|
137
101
|
.content {
|
|
138
|
-
padding:
|
|
102
|
+
padding: 30px;
|
|
139
103
|
}
|
|
140
104
|
.main-image {
|
|
141
105
|
width: 100%;
|
|
142
106
|
height: 320px;
|
|
143
107
|
background: #f0f0f0 url('${item.image_url}') center/cover;
|
|
144
|
-
border-radius:
|
|
145
|
-
margin-bottom:
|
|
108
|
+
border-radius: 15px;
|
|
109
|
+
margin-bottom: 25px;
|
|
110
|
+
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
|
|
111
|
+
border: 1px solid rgba(0,0,0,0.05);
|
|
146
112
|
}
|
|
147
113
|
.product-title {
|
|
148
|
-
font-size:
|
|
149
|
-
margin: 0 0
|
|
150
|
-
color: #
|
|
114
|
+
font-size: 26px;
|
|
115
|
+
margin: 0 0 20px 0;
|
|
116
|
+
color: #2c3e50;
|
|
117
|
+
font-weight: 700;
|
|
118
|
+
line-height: 1.4;
|
|
151
119
|
}
|
|
152
120
|
.author-section {
|
|
153
121
|
display: flex;
|
|
154
122
|
align-items: center;
|
|
155
|
-
gap:
|
|
156
|
-
margin-bottom:
|
|
123
|
+
gap: 15px;
|
|
124
|
+
margin-bottom: 25px;
|
|
125
|
+
padding: 15px;
|
|
126
|
+
background: #f8f9fa;
|
|
127
|
+
border-radius: 12px;
|
|
128
|
+
border-left: 4px solid #3498db;
|
|
157
129
|
}
|
|
158
130
|
.author-avatar {
|
|
159
|
-
width:
|
|
160
|
-
height:
|
|
131
|
+
width: 60px;
|
|
132
|
+
height: 60px;
|
|
161
133
|
border-radius: 50%;
|
|
162
|
-
border:
|
|
134
|
+
border: 3px solid #fff;
|
|
135
|
+
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
136
|
+
object-fit: cover;
|
|
137
|
+
}
|
|
138
|
+
.author-info {
|
|
139
|
+
flex: 1;
|
|
140
|
+
}
|
|
141
|
+
.author-name {
|
|
142
|
+
font-size: 18px;
|
|
143
|
+
font-weight: 600;
|
|
144
|
+
color: #2c3e50;
|
|
145
|
+
margin-bottom: 4px;
|
|
146
|
+
}
|
|
147
|
+
.author-label {
|
|
148
|
+
font-size: 14px;
|
|
149
|
+
color: #7f8c8d;
|
|
163
150
|
}
|
|
164
151
|
.price-section {
|
|
165
|
-
font-size:
|
|
152
|
+
font-size: 32px;
|
|
153
|
+
font-weight: 700;
|
|
166
154
|
color: #e74c3c;
|
|
167
|
-
margin-bottom:
|
|
155
|
+
margin-bottom: 30px;
|
|
156
|
+
text-align: center;
|
|
157
|
+
background: #fff9f9;
|
|
158
|
+
padding: 15px;
|
|
159
|
+
border-radius: 12px;
|
|
160
|
+
border: 2px dashed #e74c3c;
|
|
168
161
|
}
|
|
169
162
|
.description {
|
|
170
|
-
color: #
|
|
171
|
-
line-height: 1.
|
|
172
|
-
padding
|
|
173
|
-
|
|
163
|
+
color: #34495e;
|
|
164
|
+
line-height: 1.7;
|
|
165
|
+
padding: 20px;
|
|
166
|
+
background: #f8f9fa;
|
|
167
|
+
border-radius: 12px;
|
|
168
|
+
margin-bottom: 30px;
|
|
169
|
+
font-size: 15px;
|
|
170
|
+
}
|
|
171
|
+
.stats {
|
|
172
|
+
display: flex;
|
|
173
|
+
justify-content: space-around;
|
|
174
|
+
margin-bottom: 30px;
|
|
175
|
+
text-align: center;
|
|
176
|
+
}
|
|
177
|
+
.stat-item {
|
|
178
|
+
padding: 15px;
|
|
179
|
+
}
|
|
180
|
+
.stat-value {
|
|
181
|
+
font-size: 24px;
|
|
182
|
+
font-weight: 700;
|
|
183
|
+
color: #3498db;
|
|
184
|
+
}
|
|
185
|
+
.stat-label {
|
|
186
|
+
font-size: 14px;
|
|
187
|
+
color: #7f8c8d;
|
|
188
|
+
margin-top: 5px;
|
|
174
189
|
}
|
|
175
190
|
.related-works {
|
|
176
|
-
margin-top:
|
|
191
|
+
margin-top: 30px;
|
|
192
|
+
border-top: 1px solid #eee;
|
|
193
|
+
padding-top: 25px;
|
|
177
194
|
}
|
|
178
195
|
.related-title {
|
|
179
|
-
font-size:
|
|
180
|
-
color: #
|
|
181
|
-
margin-bottom:
|
|
196
|
+
font-size: 20px;
|
|
197
|
+
color: #2c3e50;
|
|
198
|
+
margin-bottom: 20px;
|
|
199
|
+
text-align: center;
|
|
200
|
+
font-weight: 600;
|
|
182
201
|
}
|
|
183
202
|
.works-grid {
|
|
184
203
|
display: grid;
|
|
@@ -186,33 +205,64 @@ function generateCardHTML(item, relatedItems = []) {
|
|
|
186
205
|
gap: 15px;
|
|
187
206
|
}
|
|
188
207
|
.work-item {
|
|
189
|
-
background:
|
|
190
|
-
border-radius:
|
|
208
|
+
background: white;
|
|
209
|
+
border-radius: 12px;
|
|
191
210
|
overflow: hidden;
|
|
192
|
-
|
|
211
|
+
box-shadow: 0 4px 8px rgba(0,0,0,0.08);
|
|
212
|
+
transition: all 0.3s ease;
|
|
193
213
|
}
|
|
194
214
|
.work-item:hover {
|
|
195
|
-
transform: translateY(-
|
|
215
|
+
transform: translateY(-5px);
|
|
216
|
+
box-shadow: 0 10px 20px rgba(0,0,0,0.15);
|
|
196
217
|
}
|
|
197
218
|
.work-image {
|
|
198
|
-
height:
|
|
219
|
+
height: 100px;
|
|
199
220
|
background-size: cover;
|
|
200
221
|
background-position: center;
|
|
201
222
|
}
|
|
202
223
|
.work-info {
|
|
203
|
-
padding:
|
|
224
|
+
padding: 12px;
|
|
225
|
+
}
|
|
226
|
+
.work-title {
|
|
227
|
+
font-size: 13px;
|
|
228
|
+
margin-bottom: 8px;
|
|
229
|
+
color: #2c3e50;
|
|
230
|
+
height: 36px;
|
|
231
|
+
overflow: hidden;
|
|
204
232
|
}
|
|
205
233
|
.work-price {
|
|
206
|
-
font-size:
|
|
234
|
+
font-size: 15px;
|
|
235
|
+
font-weight: 600;
|
|
207
236
|
color: #e74c3c;
|
|
208
237
|
}
|
|
209
238
|
.footer {
|
|
210
|
-
background: #
|
|
211
|
-
padding:
|
|
239
|
+
background: #2c3e50;
|
|
240
|
+
padding: 20px;
|
|
212
241
|
text-align: center;
|
|
213
|
-
color: #
|
|
242
|
+
color: #ecf0f1;
|
|
243
|
+
font-size: 14px;
|
|
244
|
+
}
|
|
245
|
+
.link {
|
|
246
|
+
color: #3498db;
|
|
247
|
+
text-decoration: none;
|
|
248
|
+
font-weight: 500;
|
|
249
|
+
}
|
|
250
|
+
.link:hover {
|
|
251
|
+
text-decoration: underline;
|
|
252
|
+
}
|
|
253
|
+
.tags {
|
|
254
|
+
display: flex;
|
|
255
|
+
flex-wrap: wrap;
|
|
256
|
+
gap: 8px;
|
|
257
|
+
margin-bottom: 25px;
|
|
258
|
+
}
|
|
259
|
+
.tag {
|
|
260
|
+
background: #e1f0fa;
|
|
261
|
+
color: #3498db;
|
|
262
|
+
padding: 6px 12px;
|
|
263
|
+
border-radius: 20px;
|
|
214
264
|
font-size: 13px;
|
|
215
|
-
|
|
265
|
+
font-weight: 500;
|
|
216
266
|
}
|
|
217
267
|
</style>
|
|
218
268
|
</head>
|
|
@@ -220,6 +270,7 @@ function generateCardHTML(item, relatedItems = []) {
|
|
|
220
270
|
<div class="container">
|
|
221
271
|
<div class="header">
|
|
222
272
|
<div class="label">NEW ARRIVAL</div>
|
|
273
|
+
<div class="booth-logo">BOOTH</div>
|
|
223
274
|
</div>
|
|
224
275
|
|
|
225
276
|
<div class="content">
|
|
@@ -229,17 +280,36 @@ function generateCardHTML(item, relatedItems = []) {
|
|
|
229
280
|
<div class="author-section">
|
|
230
281
|
<img src="${item.author_thumbnail_url || 'https://s2.booth.pm/static-images/user/guest-32.png'}"
|
|
231
282
|
class="author-avatar"
|
|
232
|
-
alt="作者头像">
|
|
233
|
-
<div>
|
|
234
|
-
<div
|
|
235
|
-
<div
|
|
283
|
+
alt="作者头像" onerror="this.src='https://s2.booth.pm/static-images/user/guest-32.png'">
|
|
284
|
+
<div class="author-info">
|
|
285
|
+
<div class="author-name">${item.author}</div>
|
|
286
|
+
<div class="author-label">BOOTHクリエイター</div>
|
|
236
287
|
</div>
|
|
237
288
|
</div>
|
|
238
289
|
|
|
239
290
|
<div class="price-section">¥${item.price.toLocaleString()}</div>
|
|
240
291
|
|
|
292
|
+
<div class="stats">
|
|
293
|
+
<div class="stat-item">
|
|
294
|
+
<div class="stat-value">${item.likes || 0}</div>
|
|
295
|
+
<div class="stat-label">收藏数</div>
|
|
296
|
+
</div>
|
|
297
|
+
<div class="stat-item">
|
|
298
|
+
<div class="stat-value">${item.category || '未分类'}</div>
|
|
299
|
+
<div class="stat-label">分类</div>
|
|
300
|
+
</div>
|
|
301
|
+
<div class="stat-item">
|
|
302
|
+
<div class="stat-value">#${item.id}</div>
|
|
303
|
+
<div class="stat-label">商品ID</div>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
<div class="tags">
|
|
308
|
+
${(item.tags || []).slice(0, 5).map(tag => `<div class="tag">${tag.name}</div>`).join('')}
|
|
309
|
+
</div>
|
|
310
|
+
|
|
241
311
|
<div class="description">
|
|
242
|
-
<p>${item.description.slice(0,
|
|
312
|
+
<p>${item.description.slice(0, 300)}${item.description.length > 300 ? '...' : ''}</p>
|
|
243
313
|
</div>
|
|
244
314
|
|
|
245
315
|
${relatedItems.length > 0 ? `
|
|
@@ -250,7 +320,7 @@ function generateCardHTML(item, relatedItems = []) {
|
|
|
250
320
|
<div class="work-item">
|
|
251
321
|
<div class="work-image" style="background-image:url('${work.image_url}')"></div>
|
|
252
322
|
<div class="work-info">
|
|
253
|
-
<div
|
|
323
|
+
<div class="work-title">${work.title.slice(0, 20)}${work.title.length > 20 ? '...' : ''}</div>
|
|
254
324
|
<div class="work-price">¥${work.price.toLocaleString()}</div>
|
|
255
325
|
</div>
|
|
256
326
|
</div>
|
|
@@ -261,16 +331,63 @@ function generateCardHTML(item, relatedItems = []) {
|
|
|
261
331
|
</div>
|
|
262
332
|
|
|
263
333
|
<div class="footer">
|
|
264
|
-
由VRCBBS提供 | BOOTH
|
|
334
|
+
由VRCBBS提供 | BOOTH链接:
|
|
265
335
|
<a href="https://booth.pm/zh-cn/items/${item.id}"
|
|
266
|
-
|
|
267
|
-
https://booth.pm/items/${item.id}
|
|
336
|
+
class="link">
|
|
337
|
+
https://booth.pm/zh-cn/items/${item.id}
|
|
268
338
|
</a>
|
|
269
339
|
</div>
|
|
270
340
|
</div>
|
|
271
341
|
</body>
|
|
272
342
|
</html>`;
|
|
273
343
|
}
|
|
344
|
+
|
|
345
|
+
async function getBoothItem(id) {
|
|
346
|
+
try {
|
|
347
|
+
const [itemRes, wishRes] = await Promise.all([
|
|
348
|
+
fetch(`https://booth.pm/zh-cn/items/${id}.json`),
|
|
349
|
+
fetch(`https://accounts.booth.pm/wish_lists.json?item_ids%5B%5D=${id}`)
|
|
350
|
+
]);
|
|
351
|
+
|
|
352
|
+
const itemData = await itemRes.json();
|
|
353
|
+
const wishData = await wishRes.json();
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
id,
|
|
357
|
+
title: itemData.name,
|
|
358
|
+
price: itemData.price,
|
|
359
|
+
image_url: itemData.images[0]?.original,
|
|
360
|
+
description: itemData.description,
|
|
361
|
+
category: itemData.category?.name,
|
|
362
|
+
parent_category: itemData.category?.parent?.name,
|
|
363
|
+
author: itemData.shop?.name,
|
|
364
|
+
author_thumbnail_url: itemData.shop?.icon?.thumb?.original || itemData.shop?.icon?.small?.original,
|
|
365
|
+
likes: wishData.wishlists_counts[id] || 0,
|
|
366
|
+
tags: itemData.tags
|
|
367
|
+
};
|
|
368
|
+
} catch (error) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async function fetchRelatedItems(author) {
|
|
374
|
+
try {
|
|
375
|
+
const res = await fetch(`https://booth.pm/zh-cn/search.json?q=${encodeURIComponent(author)}&in_stock=true`);
|
|
376
|
+
const data = await res.json();
|
|
377
|
+
return data.items
|
|
378
|
+
.filter(i => i.shop?.name === author)
|
|
379
|
+
.slice(0, 3)
|
|
380
|
+
.map(item => ({
|
|
381
|
+
id: item.id,
|
|
382
|
+
title: item.name,
|
|
383
|
+
price: item.price,
|
|
384
|
+
image_url: item.images[0]?.original,
|
|
385
|
+
}));
|
|
386
|
+
} catch (error) {
|
|
387
|
+
return [];
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
274
391
|
async function captureCard(ctx, id) {
|
|
275
392
|
const logger = ctx.logger("booth-get");
|
|
276
393
|
const item = await getBoothItem(id);
|
|
@@ -320,62 +437,76 @@ function apply(ctx, config) {
|
|
|
320
437
|
}
|
|
321
438
|
});
|
|
322
439
|
|
|
323
|
-
ctx.command("摊位名称 <query>")
|
|
324
|
-
|
|
325
|
-
|
|
440
|
+
ctx.command("摊位名称 <query>")
|
|
441
|
+
.action(async ({ session }, query) => {
|
|
442
|
+
if (!query) return "请输入商品名称";
|
|
443
|
+
|
|
444
|
+
const searchUrl = `https://booth.pm/zh-cn/search/${encodeURIComponent(query)}?in_stock=true`;
|
|
445
|
+
const page = await ctx.puppeteer.page();
|
|
446
|
+
|
|
447
|
+
try {
|
|
448
|
+
await page.goto(searchUrl, { waitUntil: 'networkidle0', timeout: 10000 });
|
|
449
|
+
const content = await page.content();
|
|
450
|
+
|
|
451
|
+
const itemRegex = /item-card__wrap" id="item_(\d+)".*?<h2 class="item-card__title">([^<]+)<\/h2>/gs;
|
|
452
|
+
const matches = [...content.matchAll(itemRegex)];
|
|
453
|
+
|
|
454
|
+
if (!matches.length) return "没有找到相关商品";
|
|
455
|
+
|
|
456
|
+
const items = matches.map(match => ({
|
|
457
|
+
id: match[1],
|
|
458
|
+
name: match[2].trim()
|
|
459
|
+
}));
|
|
460
|
+
|
|
461
|
+
const exactMatches = items.filter(item => item.name === query);
|
|
462
|
+
if (exactMatches.length === 1) {
|
|
326
463
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
464
|
+
const buffer = await captureCard(ctx, exactMatches[0].id);
|
|
465
|
+
return buffer ? import_koishi.h.image(buffer, "image/png") : "卡片生成失败";
|
|
466
|
+
} else if (exactMatches.length > 1) {
|
|
330
467
|
|
|
331
|
-
|
|
332
|
-
|
|
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})`);
|
|
468
|
+
const buffer = await captureCard(ctx, exactMatches[0].id);
|
|
469
|
+
return buffer ? import_koishi.h.image(buffer, "image/png") : "卡片生成失败";
|
|
342
470
|
}
|
|
343
|
-
}
|
|
344
|
-
const pageTitle = await page.title();
|
|
345
|
-
logger.debug('页面标题:', pageTitle);
|
|
346
471
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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 "卡片生成失败";
|
|
472
|
+
const queryLower = query.toLowerCase();
|
|
473
|
+
const scoredItems = items.map(item => ({
|
|
474
|
+
...item,
|
|
475
|
+
score: getSimilarity(queryLower, item.name.toLowerCase())
|
|
476
|
+
})).sort((a, b) => b.score - a.score);
|
|
477
|
+
|
|
478
|
+
const bestMatch = scoredItems[0];
|
|
479
|
+
if (bestMatch.score < 0.3) {
|
|
480
|
+
return `没有找到完全匹配的商品,最接近的是:${bestMatch.name}`;
|
|
366
481
|
}
|
|
367
|
-
|
|
482
|
+
|
|
483
|
+
const buffer = await captureCard(ctx, bestMatch.id);
|
|
484
|
+
return buffer ? import_koishi.h.image(buffer, "image/png") : "卡片生成失败";
|
|
485
|
+
|
|
368
486
|
} catch (error) {
|
|
369
|
-
logger.error(
|
|
370
|
-
return "
|
|
487
|
+
logger.error("搜索失败:", error);
|
|
488
|
+
return "搜索失败,请检查商品名称或稍后再试";
|
|
489
|
+
} finally {
|
|
490
|
+
await page.close();
|
|
371
491
|
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
function getSimilarity(a, b) {
|
|
495
|
+
if (a === b) return 1;
|
|
496
|
+
if (a.length < 2 || b.length < 2) return 0;
|
|
497
|
+
|
|
498
|
+
const bigramsA = new Set();
|
|
499
|
+
for (let i = 0; i < a.length - 1; i++) {
|
|
500
|
+
bigramsA.add(a.substring(i, i + 2));
|
|
377
501
|
}
|
|
378
|
-
|
|
502
|
+
|
|
503
|
+
let matches = 0;
|
|
504
|
+
for (let i = 0; i < b.length - 1; i++) {
|
|
505
|
+
if (bigramsA.has(b.substring(i, i + 2))) matches++;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return (2 * matches) / (a.length + b.length - 2);
|
|
509
|
+
}
|
|
379
510
|
|
|
380
511
|
ctx.middleware(async (session, next) => {
|
|
381
512
|
const boothUrlRegex = /https:\/\/booth.pm\/[\w-]+\/items\/(\d+)/;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-booth-get",
|
|
3
3
|
"description": "通过url与名称检查摊位物品并反馈用户搜索的图片",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.1.0",
|
|
5
5
|
"contributors": [
|
|
6
6
|
"rixiang <1148147857@qq.com>"
|
|
7
7
|
],
|
|
@@ -26,15 +26,17 @@
|
|
|
26
26
|
"chatbot",
|
|
27
27
|
"koishi",
|
|
28
28
|
"plugin",
|
|
29
|
-
"booth"
|
|
29
|
+
"booth",
|
|
30
|
+
"pngjs"
|
|
30
31
|
],
|
|
31
32
|
"peerDependencies": {
|
|
32
|
-
"koishi": "4.18.
|
|
33
|
+
"koishi": "4.18.9"
|
|
33
34
|
},
|
|
34
35
|
"dependencies": {
|
|
35
36
|
"atsc": "^2.1.0",
|
|
36
37
|
"koishi-plugin-puppeteer": "^3.9.0",
|
|
38
|
+
"list": "^2.0.19",
|
|
37
39
|
"pngjs": "^7.0.0",
|
|
38
|
-
"puppeteer-core": "^
|
|
40
|
+
"puppeteer-core": "^24.17.1"
|
|
39
41
|
}
|
|
40
|
-
}
|
|
42
|
+
}
|
package/README.md
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
我在此插件中创建了两个指令
|
|
2
|
-
分别是:
|
|
3
|
-
/摊位
|
|
4
|
-
/摊位名称
|
|
5
|
-
|
|
6
|
-
前者以id来准确查询BOOT商品
|
|
7
|
-
后者以名称查询BOOTH商品(因为搜索排列问题为人气高者为首选反馈)
|
|
8
|
-
|
|
9
|
-
内容展示:
|
|
10
|
-
|
|
11
|
-
https://www.freeimg.cn/i/2024/09/01/66d47ad049577.png
|
|
12
|
-
|
|
13
|
-
更新内容:
|
|
14
|
-
|
|
15
|
-
2.0.0版本更新自定义代理
|
|
16
|
-
|
|
17
|
-
3.0.0版本更新ws服务(目前还在测试阶段,预测5.0.0版本与大家见面)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
感谢大家的喜欢与支持
|
|
21
|
-
|
|
22
|
-
最后更新预测6.0.0版本,本版本将优化反馈图片
|
|
23
|
-
|
|
24
|
-
图片预览
|
|
25
|
-
|
|
26
|
-
https://www.freeimg.cn/i/2024/09/15/66e626ea2f552.webp
|