koishi-plugin-booth-get 6.0.3 → 6.0.5

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.
@@ -11,7 +11,386 @@ class CardGenerator {
11
11
  this.config = config;
12
12
  }
13
13
 
14
- generateCardHTML(item, relatedItems = []) {
14
+ generateCardHTML(item, relatedItems = [], customConfig = {}) {
15
+ const cardWidth = customConfig.cardWidth || 900;
16
+ const leftColumnWidth = customConfig.leftColumnWidth || 280;
17
+ const topGradientColor1 = customConfig.topGradientColor1 || '#e8bdf6';
18
+ const topGradientColor2 = customConfig.topGradientColor2 || '#fceabb';
19
+ const footerGradientColor1 = customConfig.footerGradientColor1 || '#fceabb';
20
+ const footerGradientColor2 = customConfig.footerGradientColor2 || '#e8bdf6';
21
+ const priceTagBgColor = customConfig.priceTagBgColor || '#ff66b2';
22
+ const infoBubbleBgColor = customConfig.infoBubbleBgColor || '#aafcf5';
23
+ const textColor = customConfig.textColor || '#d63384';
24
+ const shopBannerBgColor = customConfig.shopBannerBgColor || '#fcfcd0';
25
+
26
+ return `
27
+ <!DOCTYPE html>
28
+ <html lang="zh-CN">
29
+ <head>
30
+ <meta charset="UTF-8">
31
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
32
+ <title>BOOTH Card Replica</title>
33
+ <style>
34
+ * {
35
+ box-sizing: border-box;
36
+ margin: 0;
37
+ padding: 0;
38
+ }
39
+
40
+ body {
41
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
42
+ background-color: #f0f0f0;
43
+ display: flex;
44
+ justify-content: center;
45
+ align-items: center;
46
+ min-height: 100vh;
47
+ padding: 20px;
48
+ }
49
+
50
+ .card-container {
51
+ width: ${cardWidth}px;
52
+ max-width: 100%;
53
+ background-color: #fff;
54
+ border-radius: 30px;
55
+ overflow: hidden;
56
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
57
+ position: relative;
58
+ }
59
+
60
+ .top-gradient-bg {
61
+ height: 60px;
62
+ background: linear-gradient(to right, ${topGradientColor1}, ${topGradientColor2});
63
+ position: absolute;
64
+ top: 0;
65
+ left: 0;
66
+ right: 0;
67
+ z-index: 0;
68
+ }
69
+
70
+ .header-logo-area {
71
+ position: absolute;
72
+ top: 20px;
73
+ right: 30px;
74
+ z-index: 10;
75
+ display: flex;
76
+ align-items: center;
77
+ gap: 8px;
78
+ }
79
+
80
+ .booth-text {
81
+ font-weight: bold;
82
+ font-size: 18px;
83
+ color: #333;
84
+ }
85
+
86
+ .booth-icon {
87
+ width: 30px;
88
+ height: 30px;
89
+ background-color: #ff4d4d;
90
+ border-radius: 4px;
91
+ display: flex;
92
+ justify-content: center;
93
+ align-items: center;
94
+ color: white;
95
+ font-size: 12px;
96
+ }
97
+
98
+ .content-wrapper {
99
+ display: flex;
100
+ padding: 20px 30px 40px 30px;
101
+ position: relative;
102
+ z-index: 5;
103
+ margin-top: 40px;
104
+ }
105
+
106
+ .left-column {
107
+ width: ${leftColumnWidth}px;
108
+ flex-shrink: 0;
109
+ margin-right: 30px;
110
+ }
111
+
112
+ .main-image {
113
+ width: 100%;
114
+ aspect-ratio: 1/1;
115
+ background-color: #ddd;
116
+ border-radius: 8px;
117
+ object-fit: cover;
118
+ margin-bottom: 10px;
119
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
120
+ }
121
+
122
+ .price-row {
123
+ display: flex;
124
+ align-items: center;
125
+ margin-bottom: 10px;
126
+ }
127
+
128
+ .price-tag {
129
+ background-color: ${priceTagBgColor};
130
+ color: white;
131
+ font-weight: bold;
132
+ font-size: 20px;
133
+ padding: 5px 10px;
134
+ border-radius: 4px;
135
+ margin-right: 15px;
136
+ }
137
+
138
+ .like-count {
139
+ display: flex;
140
+ align-items: center;
141
+ color: #666;
142
+ font-weight: bold;
143
+ font-size: 16px;
144
+ }
145
+
146
+ .heart-icon {
147
+ color: #333;
148
+ margin-right: 5px;
149
+ font-size: 18px;
150
+ }
151
+
152
+ .item-title {
153
+ font-size: 14px;
154
+ color: #333;
155
+ line-height: 1.4;
156
+ margin-bottom: 5px;
157
+ }
158
+
159
+ .item-subtitle {
160
+ font-size: 12px;
161
+ color: #666;
162
+ margin-bottom: 20px;
163
+ }
164
+
165
+ .other-works-title {
166
+ font-size: 14px;
167
+ font-weight: bold;
168
+ color: #333;
169
+ margin-bottom: 10px;
170
+ }
171
+
172
+ .thumbnails {
173
+ display: flex;
174
+ gap: 10px;
175
+ }
176
+
177
+ .thumb-img {
178
+ width: 70px;
179
+ height: 70px;
180
+ background-color: #eee;
181
+ border-radius: 6px;
182
+ object-fit: cover;
183
+ border: 1px solid #eee;
184
+ }
185
+
186
+ .right-column {
187
+ flex-grow: 1;
188
+ display: flex;
189
+ flex-direction: column;
190
+ }
191
+
192
+ .shop-banner {
193
+ width: 100%;
194
+ height: 80px;
195
+ background-color: ${shopBannerBgColor};
196
+ border-radius: 8px;
197
+ margin-bottom: 20px;
198
+ display: flex;
199
+ align-items: center;
200
+ justify-content: space-between;
201
+ padding: 0 20px;
202
+ position: relative;
203
+ overflow: hidden;
204
+ border: 1px solid #eee;
205
+ }
206
+
207
+ .banner-text {
208
+ font-family: 'Courier New', Courier, monospace;
209
+ font-weight: bold;
210
+ font-size: 24px;
211
+ color: #fff;
212
+ text-shadow: 1px 1px 0 #aaa;
213
+ letter-spacing: 1px;
214
+ }
215
+
216
+ .banner-char {
217
+ height: 90%;
218
+ position: absolute;
219
+ right: 0;
220
+ bottom: 0;
221
+ }
222
+
223
+ .author-info {
224
+ display: flex;
225
+ align-items: center;
226
+ margin-bottom: 20px;
227
+ }
228
+
229
+ .author-avatar {
230
+ width: 40px;
231
+ height: 40px;
232
+ border-radius: 50%;
233
+ background-color: #ddd;
234
+ margin-right: 10px;
235
+ object-fit: cover;
236
+ }
237
+
238
+ .author-name {
239
+ color: ${textColor};
240
+ font-weight: bold;
241
+ font-size: 14px;
242
+ }
243
+
244
+ .info-bubble {
245
+ background-color: ${infoBubbleBgColor};
246
+ border-radius: 15px;
247
+ padding: 25px;
248
+ color: ${textColor};
249
+ font-size: 13px;
250
+ line-height: 1.6;
251
+ position: relative;
252
+ }
253
+
254
+ .bubble-header {
255
+ font-weight: bold;
256
+ margin-bottom: 10px;
257
+ display: block;
258
+ }
259
+
260
+ .bubble-text p {
261
+ margin-bottom: 10px;
262
+ }
263
+
264
+ .bubble-link {
265
+ display: block;
266
+ margin-top: 20px;
267
+ text-align: right;
268
+ color: ${textColor};
269
+ font-weight: bold;
270
+ text-decoration: none;
271
+ font-size: 14px;
272
+ }
273
+
274
+ .footer-bar {
275
+ background: linear-gradient(to right, ${footerGradientColor1}, ${footerGradientColor2});
276
+ padding: 15px 30px;
277
+ display: flex;
278
+ justify-content: space-between;
279
+ align-items: center;
280
+ border-top: 1px solid rgba(0,0,0,0.05);
281
+ }
282
+
283
+ .footer-left {
284
+ display: flex;
285
+ align-items: center;
286
+ gap: 8px;
287
+ }
288
+
289
+ .footer-center {
290
+ font-weight: bold;
291
+ color: #333;
292
+ font-size: 14px;
293
+ }
294
+
295
+ @media (max-width: 768px) {
296
+ .content-wrapper {
297
+ flex-direction: column;
298
+ }
299
+ .left-column {
300
+ width: 100%;
301
+ margin-right: 0;
302
+ margin-bottom: 20px;
303
+ }
304
+ .shop-banner {
305
+ height: 60px;
306
+ }
307
+ .banner-text {
308
+ font-size: 18px;
309
+ }
310
+ }
311
+ </style>
312
+ </head>
313
+ <body>
314
+ <div class="card-container">
315
+ <div class="top-gradient-bg"></div>
316
+
317
+ <div class="header-logo-area">
318
+ <span class="booth-text">BOOTH</span>
319
+ <div class="booth-icon">
320
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="white">
321
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>
322
+ </svg>
323
+ </div>
324
+ </div>
325
+
326
+ <div class="content-wrapper">
327
+ <div class="left-column">
328
+ <img src="${item.image_url}" alt="${item.title || 'Item'}" class="main-image" onerror="this.style.backgroundColor='#f0f0f0'; this.alt='图片加载失败'">
329
+
330
+ <div class="price-row">
331
+ <div class="price-tag">¥${item.price ? item.price.toLocaleString() : '0'}</div>
332
+ <div class="like-count">
333
+ <span class="heart-icon">♥</span> ${item.likes || 0}
334
+ </div>
335
+ </div>
336
+
337
+ <div class="item-title">${item.title || 'Untitled Item'}</div>
338
+ <div class="item-subtitle">${item.category || '3D Models/3D Characters'}</div>
339
+
340
+ <div class="other-works-title">其余作品</div>
341
+ <div class="thumbnails">
342
+ ${(relatedItems || []).slice(0, 3).map(work => {
343
+ if (work.image_url && work.image_url.trim() !== '') {
344
+ return `<img src="${work.image_url}" class="thumb-img" onerror="this.style.display='none'">`;
345
+ }
346
+ return '';
347
+ }).filter(Boolean).join('')}
348
+ ${Array(Math.max(0, 3 - (relatedItems || []).filter(w => w.image_url && w.image_url.trim() !== '').length)).fill(0).map((_, i) => `
349
+ <div style="width:70px;height:70px;background:#f0f0f0;border-radius:6px;"></div>
350
+ `).join('')}
351
+ </div>
352
+ </div>
353
+
354
+ <div class="right-column">
355
+ <div class="shop-banner">
356
+ <div class="banner-text">${item.author || 'Unknown'}'s SHOP</div>
357
+ ${item.author_thumbnail_url ? `<img src="${item.author_thumbnail_url}" class="banner-char" style="opacity: 0.5; height:90%; position:absolute; right:0; bottom:0;" onerror="this.style.display='none'">` : ''}
358
+ </div>
359
+
360
+ <div class="author-info">
361
+ ${item.author_thumbnail_url ? `<img src="${item.author_thumbnail_url}" alt="Avatar" class="author-avatar" onerror="this.style.backgroundColor='#ddd'">` : '<div class="author-avatar" style="background:#ddd;"></div>'}
362
+ <span class="author-name">作者:${item.author || 'Unknown'}</span>
363
+ </div>
364
+
365
+ <div class="info-bubble">
366
+ <span class="bubble-header">🏠 商品说明 🏠</span>
367
+ <div class="bubble-text">
368
+ <p>${(item.description || '暂无详细说明').slice(0, 200)}${(item.description || '').length > 200 ? '...' : ''}</p>
369
+ </div>
370
+ <a href="https://booth.pm/zh-cn/items/${item.id}" class="bubble-link">链接:https://booth.pm/zh-cn/items/${item.id}</a>
371
+ </div>
372
+ </div>
373
+ </div>
374
+
375
+ <div class="footer-bar">
376
+ <div class="footer-left">
377
+ <span class="booth-text">BOOTH</span>
378
+ <div class="booth-icon">
379
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="white">
380
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>
381
+ </svg>
382
+ </div>
383
+ </div>
384
+ <div class="footer-center">
385
+ 由乃花生成 | BOOTH 提供
386
+ </div>
387
+ </div>
388
+ </div>
389
+ </body>
390
+ </html>`;
391
+ }
392
+
393
+ generateDiscountCardHTML(item) {
15
394
  return `
16
395
  <html>
17
396
  <head>
@@ -22,7 +401,7 @@ class CardGenerator {
22
401
  margin: 0;
23
402
  padding: 0;
24
403
  font-family: 'Noto Sans SC', sans-serif;
25
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
404
+ background: linear-gradient(135deg, #ff6b6b 0%, #ffa502 100%);
26
405
  display: flex;
27
406
  justify-content: center;
28
407
  align-items: center;
@@ -43,15 +422,17 @@ class CardGenerator {
43
422
  position: relative;
44
423
  color: white;
45
424
  }
46
- .header::before {
47
- content: "";
425
+ .discount-badge {
48
426
  position: absolute;
49
- top: 0;
50
- left: 0;
51
- right: 0;
52
- bottom: 0;
53
- 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>');
54
- background-size: 100px 100px;
427
+ top: 20px;
428
+ right: 20px;
429
+ background: #e74c3c;
430
+ color: white;
431
+ padding: 10px 15px;
432
+ border-radius: 30px;
433
+ font-weight: 700;
434
+ font-size: 18px;
435
+ box-shadow: 0 4px 8px rgba(0,0,0,0.2);
55
436
  }
56
437
  .label {
57
438
  background: rgba(255, 255, 255, 0.2);
@@ -90,6 +471,33 @@ class CardGenerator {
90
471
  font-weight: 700;
91
472
  line-height: 1.4;
92
473
  }
474
+ .price-section {
475
+ display: flex;
476
+ align-items: center;
477
+ justify-content: center;
478
+ gap: 20px;
479
+ margin-bottom: 30px;
480
+ padding: 20px;
481
+ background: #fff9f9;
482
+ border-radius: 12px;
483
+ border: 2px dashed #e74c3c;
484
+ }
485
+ .original-price {
486
+ font-size: 24px;
487
+ color: #7f8c8d;
488
+ text-decoration: line-through;
489
+ }
490
+ .current-price {
491
+ font-size: 32px;
492
+ font-weight: 700;
493
+ color: #e74c3c;
494
+ }
495
+ .discount-info {
496
+ text-align: center;
497
+ font-size: 18px;
498
+ color: #e74c3c;
499
+ font-weight: 600;
500
+ }
93
501
  .author-section {
94
502
  display: flex;
95
503
  align-items: center;
@@ -117,96 +525,19 @@ class CardGenerator {
117
525
  color: #2c3e50;
118
526
  margin-bottom: 4px;
119
527
  }
120
- .author-label {
121
- font-size: 14px;
122
- color: #7f8c8d;
123
- }
124
- .price-section {
125
- font-size: 32px;
126
- font-weight: 700;
127
- color: #e74c3c;
128
- margin-bottom: 30px;
129
- text-align: center;
130
- background: #fff9f9;
131
- padding: 15px;
132
- border-radius: 12px;
133
- border: 2px dashed #e74c3c;
134
- }
135
- .description {
136
- color: #34495e;
137
- line-height: 1.7;
138
- padding: 20px;
139
- background: #f8f9fa;
140
- border-radius: 12px;
141
- margin-bottom: 30px;
142
- font-size: 15px;
143
- }
144
- .stats {
528
+ .tags {
145
529
  display: flex;
146
- justify-content: space-around;
147
- margin-bottom: 30px;
148
- text-align: center;
149
- }
150
- .stat-item {
151
- padding: 15px;
530
+ flex-wrap: wrap;
531
+ gap: 8px;
532
+ margin-bottom: 25px;
152
533
  }
153
- .stat-value {
154
- font-size: 24px;
155
- font-weight: 700;
534
+ .tag {
535
+ background: #e1f0fa;
156
536
  color: #3498db;
157
- }
158
- .stat-label {
159
- font-size: 14px;
160
- color: #7f8c8d;
161
- margin-top: 5px;
162
- }
163
- .related-works {
164
- margin-top: 30px;
165
- border-top: 1px solid #eee;
166
- padding-top: 25px;
167
- }
168
- .related-title {
169
- font-size: 20px;
170
- color: #2c3e50;
171
- margin-bottom: 20px;
172
- text-align: center;
173
- font-weight: 600;
174
- }
175
- .works-grid {
176
- display: grid;
177
- grid-template-columns: repeat(3, 1fr);
178
- gap: 15px;
179
- }
180
- .work-item {
181
- background: white;
182
- border-radius: 12px;
183
- overflow: hidden;
184
- box-shadow: 0 4px 8px rgba(0,0,0,0.08);
185
- transition: all 0.3s ease;
186
- }
187
- .work-item:hover {
188
- transform: translateY(-5px);
189
- box-shadow: 0 10px 20px rgba(0,0,0,0.15);
190
- }
191
- .work-image {
192
- height: 100px;
193
- background-size: cover;
194
- background-position: center;
195
- }
196
- .work-info {
197
- padding: 12px;
198
- }
199
- .work-title {
537
+ padding: 6px 12px;
538
+ border-radius: 20px;
200
539
  font-size: 13px;
201
- margin-bottom: 8px;
202
- color: #2c3e50;
203
- height: 36px;
204
- overflow: hidden;
205
- }
206
- .work-price {
207
- font-size: 15px;
208
- font-weight: 600;
209
- color: #e74c3c;
540
+ font-weight: 500;
210
541
  }
211
542
  .footer {
212
543
  background: #2c3e50;
@@ -220,29 +551,13 @@ class CardGenerator {
220
551
  text-decoration: none;
221
552
  font-weight: 500;
222
553
  }
223
- .link:hover {
224
- text-decoration: underline;
225
- }
226
- .tags {
227
- display: flex;
228
- flex-wrap: wrap;
229
- gap: 8px;
230
- margin-bottom: 25px;
231
- }
232
- .tag {
233
- background: #e1f0fa;
234
- color: #3498db;
235
- padding: 6px 12px;
236
- border-radius: 20px;
237
- font-size: 13px;
238
- font-weight: 500;
239
- }
240
554
  </style>
241
555
  </head>
242
556
  <body>
243
557
  <div class="container">
244
558
  <div class="header">
245
- <div class="label">NEW ARRIVAL</div>
559
+ <div class="discount-badge">-${item.discount_rate}% OFF</div>
560
+ <div class="label">DISCOUNT ITEM</div>
246
561
  <div class="booth-logo">BOOTH</div>
247
562
  </div>
248
563
 
@@ -260,95 +575,105 @@ class CardGenerator {
260
575
  </div>
261
576
  </div>
262
577
 
263
- <div class="price-section">¥${item.price.toLocaleString()}</div>
578
+ <div class="price-section">
579
+ <div class="original-price">¥${item.original_price.toLocaleString()}</div>
580
+ <div class="current-price">¥${item.price.toLocaleString()}</div>
581
+ </div>
264
582
 
265
- <div class="stats">
266
- <div class="stat-item">
267
- <div class="stat-value">${item.likes || 0}</div>
268
- <div class="stat-label">收藏数</div>
269
- </div>
270
- <div class="stat-item">
271
- <div class="stat-value">${item.category || '未分类'}</div>
272
- <div class="stat-label">分类</div>
273
- </div>
274
- <div class="stat-item">
275
- <div class="stat-value">#${item.id}</div>
276
- <div class="stat-label">商品ID</div>
277
- </div>
583
+ <div class="discount-info">
584
+ 节省 ¥${(item.original_price - item.price).toLocaleString()} (${item.discount_rate}% 折扣)
278
585
  </div>
279
586
 
280
587
  <div class="tags">
281
588
  ${(item.tags || []).slice(0, 5).map(tag => `<div class="tag">${tag.name}</div>`).join('')}
282
589
  </div>
283
-
284
- <div class="description">
285
- <p>${(item.description || "").slice(0, 300)}${(item.description||"").length > 300 ? '...' : ''}</p>
286
- </div>
287
-
288
- ${relatedItems && relatedItems.length > 0 ? `
289
- <div class="related-works">
290
- <h3 class="related-title">同じ作者の作品</h3>
291
- <div class="works-grid">
292
- ${relatedItems.map(work => `
293
- <div class="work-item">
294
- <div class="work-image" style="background-image:url('${work.image_url}')"></div>
295
- <div class="work-info">
296
- <div class="work-title">${work.title.slice(0, 20)}${work.title.length > 20 ? '...' : ''}</div>
297
- <div class="work-price">¥${work.price?.toLocaleString?.() ?? work.price}</div>
298
- </div>
299
- </div>
300
- `).join('')}
301
- </div>
302
- </div>
303
- ` : ''}
304
590
  </div>
305
591
 
306
592
  <div class="footer">
307
- 由VRCBBS提供 | BOOTH链接:
308
- <a href="https://booth.pm/zh-cn/items/${item.id}"
309
- class="link">
310
- https://booth.pm/zh-cn/items/${item.id}
311
- </a>
593
+ 由VRCBBS提供 | 商品链接:
594
+ <a href="${item.url}" class="link">${item.url}</a>
312
595
  </div>
313
596
  </div>
314
597
  </body>
315
598
  </html>`;
316
599
  }
317
600
 
318
- generateDiscountText(item) {
319
- const savedAmount = item.original_price - item.price;
320
- const discountRate = Math.round((1 - item.price/item.original_price)*100);
321
-
322
- return `🔥 BOOTH 折扣商品推送 🔥
323
-
324
- 📌 商品名称: ${item.title}
325
- 👤 作者: ${item.author}
326
- 💰 价格: ¥${item.price.toLocaleString()} (原价: ¥${item.original_price.toLocaleString()})
327
- 💥 折扣: -${discountRate}% (节省 ¥${savedAmount.toLocaleString()})
328
- ❤️ 收藏数: ${item.likes || 0}
329
- 🏷️ 分类: ${item.category || '未分类'}
330
- #${item.id}
331
-
332
- 📝 商品简介:
333
- ${(item.description || "").slice(0, 200)}${(item.description||"").length > 200 ? '...' : ''}
334
-
335
- 🔗 商品链接: https://booth.pm/zh-cn/items/${item.id}`;
336
- }
337
-
338
601
  async captureCardHTML(html, config) {
339
602
  const page = await this.ctx.puppeteer.page();
340
603
  try {
341
- page.setDefaultTimeout(config.loadTimeout || 30000);
342
-
604
+ await page.setRequestInterception(true);
605
+ page.on('request', (request) => request.continue());
606
+
343
607
  await page.setContent(html, {
344
- waitUntil: 'networkidle0',
345
- timeout: config.loadTimeout || 30000
608
+ waitUntil: 'domcontentloaded',
609
+ timeout: config.loadTimeout
346
610
  });
347
611
 
348
- await new Promise(resolve => setTimeout(resolve, 2000));
612
+ // 获取卡片实际宽度
613
+ const cardWidth = config.cardWidth || 900;
614
+ await page.setViewport({ width: cardWidth + 40, height: 1600 });
349
615
 
350
- await page.setViewport({ width: 640, height: 1200 });
351
- const container = await page.$('.container') || await page.$('body');
616
+ // 等待所有图片加载完成(包括主图、头像、缩略图等)
617
+ const imageStatus = await page.evaluate(() => {
618
+ return new Promise((resolve) => {
619
+ const images = Array.from(document.querySelectorAll('img'));
620
+ if (images.length === 0) {
621
+ resolve({ total: 0, loaded: 0, failed: 0, urls: [] });
622
+ return;
623
+ }
624
+
625
+ let loadedCount = 0;
626
+ let failedCount = 0;
627
+ const totalImages = images.length;
628
+ const results = [];
629
+
630
+ images.forEach((img, index) => {
631
+ const url = img.src;
632
+ results.push({ url, status: 'pending' });
633
+
634
+ if (img.complete) {
635
+ if (img.naturalHeight !== 0) {
636
+ loadedCount++;
637
+ results[index].status = 'loaded';
638
+ } else {
639
+ failedCount++;
640
+ results[index].status = 'failed';
641
+ }
642
+
643
+ if (loadedCount + failedCount === totalImages) {
644
+ resolve({ total: totalImages, loaded: loadedCount, failed: failedCount, urls: results });
645
+ }
646
+ } else {
647
+ img.onload = () => {
648
+ loadedCount++;
649
+ results[index].status = 'loaded';
650
+ if (loadedCount + failedCount === totalImages) {
651
+ resolve({ total: totalImages, loaded: loadedCount, failed: failedCount, urls: results });
652
+ }
653
+ };
654
+ img.onerror = () => {
655
+ failedCount++;
656
+ results[index].status = 'failed';
657
+ if (loadedCount + failedCount === totalImages) {
658
+ resolve({ total: totalImages, loaded: loadedCount, failed: failedCount, urls: results });
659
+ }
660
+ };
661
+ }
662
+ });
663
+
664
+ // 设置超时,最多等待8秒
665
+ setTimeout(() => {
666
+ resolve({ total: totalImages, loaded: loadedCount, failed: failedCount, urls: results });
667
+ }, 8000);
668
+ });
669
+ });
670
+
671
+ this.ctx.logger("booth-get").info(`图片加载状态:`, imageStatus);
672
+
673
+ // 额外等待一小段时间确保渲染完成
674
+ await new Promise(resolve => setTimeout(resolve, 1000));
675
+
676
+ const container = await page.$('.card-container') || await page.$('body');
352
677
  return await container.screenshot({
353
678
  type: 'png',
354
679
  encoding: 'binary',