koishi-plugin-booth-get 6.0.2 → 6.0.4

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,407 +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
- generateDiscountCardHTML(item) {
319
- const savedAmount = item.original_price - item.price;
320
- const discountRate = Math.round((1 - item.price/item.original_price)*100);
321
-
322
- return `
323
- <html>
324
- <head>
325
- <meta charset="utf-8">
326
- <style>
327
- @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&family=Montserrat:wght@600;700;800&display=swap');
328
- body {
329
- margin: 0;
330
- padding: 0;
331
- font-family: 'Noto Sans SC', sans-serif;
332
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
333
- display: flex;
334
- justify-content: center;
335
- align-items: center;
336
- min-height: 100vh;
337
- }
338
- .container {
339
- width: 640px;
340
- background: white;
341
- border-radius: 20px;
342
- overflow: hidden;
343
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
344
- position: relative;
345
- }
346
- .header {
347
- background: linear-gradient(90deg, #ff6b6b, #ffa502);
348
- padding: 25px;
349
- text-align: center;
350
- position: relative;
351
- color: white;
352
- }
353
- .header::before {
354
- content: "";
355
- position: absolute;
356
- top: 0;
357
- left: 0;
358
- right: 0;
359
- bottom: 0;
360
- 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>');
361
- background-size: 100px 100px;
362
- }
363
- .label {
364
- background: rgba(255, 255, 255, 0.2);
365
- backdrop-filter: blur(10px);
366
- padding: 8px 20px;
367
- border-radius: 30px;
368
- font-size: 14px;
369
- font-weight: 500;
370
- display: inline-block;
371
- margin-bottom: 15px;
372
- border: 1px solid rgba(255, 255, 255, 0.3);
373
- }
374
- .booth-logo {
375
- font-family: 'Montserrat', sans-serif;
376
- font-weight: 800;
377
- font-size: 36px;
378
- letter-spacing: 2px;
379
- text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
380
- }
381
- .content {
382
- padding: 30px;
383
- }
384
- .main-image {
385
- width: 100%;
386
- height: 320px;
387
- background: #f0f0f0 url('${item.image_url}') center/cover;
388
- border-radius: 15px;
389
- margin-bottom: 25px;
390
- box-shadow: 0 10px 20px rgba(0,0,0,0.1);
391
- border: 1px solid rgba(0,0,0,0.05);
392
- }
393
- .product-title {
394
- font-size: 26px;
395
- margin: 0 0 20px 0;
396
- color: #2c3e50;
397
- font-weight: 700;
398
- line-height: 1.4;
399
- }
400
- .author-section {
401
- display: flex;
402
- align-items: center;
403
- gap: 15px;
404
- margin-bottom: 25px;
405
- padding: 15px;
406
- background: #f8f9fa;
407
- border-radius: 12px;
408
- border-left: 4px solid #3498db;
409
- }
410
- .author-avatar {
411
- width: 60px;
412
- height: 60px;
413
- border-radius: 50%;
414
- border: 3px solid #fff;
415
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
416
- object-fit: cover;
417
- }
418
- .author-info {
419
- flex: 1;
420
- }
421
- .author-name {
422
- font-size: 18px;
423
- font-weight: 600;
424
- color: #2c3e50;
425
- margin-bottom: 4px;
426
- }
427
- .author-label {
428
- font-size: 14px;
429
- color: #7f8c8d;
430
- }
431
- .price-section {
432
- font-size: 32px;
433
- font-weight: 700;
434
- color: #e74c3c;
435
- margin-bottom: 30px;
436
- text-align: center;
437
- background: #fff9f9;
438
- padding: 15px;
439
- border-radius: 12px;
440
- border: 2px dashed #e74c3c;
441
- }
442
- /* 新增价格对比样式 */
443
- .price-comparison {
444
- display: flex;
445
- justify-content: center;
446
- align-items: center;
447
- gap: 15px;
448
- margin-bottom: 30px;
449
- text-align: center;
450
- }
451
- .original-price {
452
- font-size: 24px;
453
- color: #7f8c8d;
454
- text-decoration: line-through;
455
- }
456
- .current-price {
457
- font-size: 32px;
458
- font-weight: 700;
459
- color: #e74c3c;
460
- }
461
- .discount-badge {
462
- background: #e74c3c;
463
- color: white;
464
- padding: 5px 10px;
465
- border-radius: 15px;
466
- font-size: 16px;
467
- font-weight: 600;
468
- }
469
- .savings {
470
- text-align: center;
471
- font-size: 16px;
472
- color: #27ae60;
473
- font-weight: 600;
474
- margin-bottom: 20px;
475
- }
476
- .description {
477
- color: #34495e;
478
- line-height: 1.7;
479
- padding: 20px;
480
- background: #f8f9fa;
481
- border-radius: 12px;
482
- margin-bottom: 30px;
483
- font-size: 15px;
484
- }
485
- .stats {
486
- display: flex;
487
- justify-content: space-around;
488
- margin-bottom: 30px;
489
- text-align: center;
490
- }
491
- .stat-item {
492
- padding: 15px;
493
- }
494
- .stat-value {
495
- font-size: 24px;
496
- font-weight: 700;
497
- color: #3498db;
498
- }
499
- .stat-label {
500
- font-size: 14px;
501
- color: #7f8c8d;
502
- margin-top: 5px;
503
- }
504
- .related-works {
505
- margin-top: 30px;
506
- border-top: 1px solid #eee;
507
- padding-top: 25px;
508
- }
509
- .related-title {
510
- font-size: 20px;
511
- color: #2c3e50;
512
- margin-bottom: 20px;
513
- text-align: center;
514
- font-weight: 600;
515
- }
516
- .works-grid {
517
- display: grid;
518
- grid-template-columns: repeat(3, 1fr);
519
- gap: 15px;
520
- }
521
- .work-item {
522
- background: white;
523
- border-radius: 12px;
524
- overflow: hidden;
525
- box-shadow: 0 4px 8px rgba(0,0,0,0.08);
526
- transition: all 0.3s ease;
527
- }
528
- .work-item:hover {
529
- transform: translateY(-5px);
530
- box-shadow: 0 10px 20px rgba(0,0,0,0.15);
531
- }
532
- .work-image {
533
- height: 100px;
534
- background-size: cover;
535
- background-position: center;
536
- }
537
- .work-info {
538
- padding: 12px;
539
- }
540
- .work-title {
541
- font-size: 13px;
542
- margin-bottom: 8px;
543
- color: #2c3e50;
544
- height: 36px;
545
- overflow: hidden;
546
- }
547
- .work-price {
548
- font-size: 15px;
549
- font-weight: 600;
550
- color: #e74c3c;
551
- }
552
- .footer {
553
- background: #2c3e50;
554
- padding: 20px;
555
- text-align: center;
556
- color: #ecf0f1;
557
- font-size: 14px;
558
- }
559
- .link {
560
- color: #3498db;
561
- text-decoration: none;
562
- font-weight: 500;
563
- }
564
- .link:hover {
565
- text-decoration: underline;
566
- }
567
- .tags {
568
- display: flex;
569
- flex-wrap: wrap;
570
- gap: 8px;
571
- margin-bottom: 25px;
572
- }
573
- .tag {
574
- background: #e1f0fa;
575
- color: #3498db;
576
- padding: 6px 12px;
577
- border-radius: 20px;
578
- font-size: 13px;
579
- font-weight: 500;
580
- }
581
- </style>
582
- </head>
583
- <body>
584
- <div class="container">
585
- <div class="header">
586
- <div class="label">折扣商品 -${discountRate}%</div>
587
- <div class="booth-logo">BOOTH</div>
588
- </div>
589
-
590
- <div class="content">
591
- <div class="main-image"></div>
592
- <h1 class="product-title">${item.title}</h1>
593
-
594
- <div class="author-section">
595
- <img src="${item.author_thumbnail_url || 'https://s2.booth.pm/static-images/user/guest-32.png'}"
596
- class="author-avatar"
597
- alt="作者头像" onerror="this.src='https://s2.booth.pm/static-images/user/guest-32.png'">
598
- <div class="author-info">
599
- <div class="author-name">${item.author}</div>
600
- <div class="author-label">BOOTHクリエイター</div>
601
- </div>
602
- </div>
603
-
604
- <!-- 折扣价格对比显示 -->
605
- <div class="price-comparison">
606
- <div class="original-price">¥${item.original_price.toLocaleString()}</div>
607
- <div class="discount-badge">-${discountRate}%</div>
608
- <div class="current-price">¥${item.price.toLocaleString()}</div>
609
- </div>
610
-
611
- <div class="savings">
612
- 节省 ¥${savedAmount.toLocaleString()}
613
- </div>
614
-
615
- <div class="stats">
616
- <div class="stat-item">
617
- <div class="stat-value">${item.likes || 0}</div>
618
- <div class="stat-label">收藏数</div>
619
- </div>
620
- <div class="stat-item">
621
- <div class="stat-value">${item.category || '未分类'}</div>
622
- <div class="stat-label">分类</div>
623
- </div>
624
- <div class="stat-item">
625
- <div class="stat-value">#${item.id}</div>
626
- <div class="stat-label">商品ID</div>
627
- </div>
628
- </div>
629
-
630
- <div class="tags">
631
- ${(item.tags || []).slice(0, 5).map(tag => `<div class="tag">${tag.name}</div>`).join('')}
632
- </div>
633
-
634
- <div class="description">
635
- <p>${(item.description || "").slice(0, 300)}${(item.description||"").length > 300 ? '...' : ''}</p>
636
- </div>
637
- </div>
638
-
639
- <div class="footer">
640
- 由VRCBBS提供 | BOOTH链接:
641
- <a href="https://booth.pm/zh-cn/items/${item.id}"
642
- class="link">
643
- https://booth.pm/zh-cn/items/${item.id}
644
- </a>
645
- </div>
646
- </div>
647
- </body>
648
- </html>\``;
649
- }
650
-
651
601
  async captureCardHTML(html, config) {
652
602
  const page = await this.ctx.puppeteer.page();
653
603
  try {
604
+ await page.setRequestInterception(true);
605
+ page.on('request', (request) => request.continue());
654
606
 
655
607
  await page.setContent(html, {
656
- waitUntil: 'networkidle0',
657
- timeout: config.loadTimeout || 30000
608
+ waitUntil: 'domcontentloaded',
609
+ timeout: config.loadTimeout
610
+ });
611
+
612
+ // 获取卡片实际宽度
613
+ const cardWidth = config.cardWidth || 900;
614
+ await page.setViewport({ width: cardWidth + 40, height: 1600 });
615
+
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
+ });
658
669
  });
659
670
 
660
- await new Promise(resolve => setTimeout(resolve, 2000));
671
+ this.ctx.logger("booth-get").info(`图片加载状态:`, imageStatus);
672
+
673
+ // 额外等待一小段时间确保渲染完成
674
+ await new Promise(resolve => setTimeout(resolve, 1000));
661
675
 
662
- await page.setViewport({ width: 640, height: 1200 });
663
- const container = await page.$('.container') || await page.$('body');
676
+ const container = await page.$('.card-container') || await page.$('body');
664
677
  return await container.screenshot({
665
678
  type: 'png',
666
679
  encoding: 'binary',