koishi-plugin-booth-get 6.0.1 → 6.0.3
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/card-generator.js +21 -211
- package/lib/discount-tracker.js +34 -52
- package/package.json +1 -1
package/lib/card-generator.js
CHANGED
|
@@ -315,228 +315,38 @@ class CardGenerator {
|
|
|
315
315
|
</html>`;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
<head>
|
|
324
|
-
<meta charset="utf-8">
|
|
325
|
-
<style>
|
|
326
|
-
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&family=Montserrat:wght@600;700;800&display=swap');
|
|
327
|
-
body {
|
|
328
|
-
margin: 0;
|
|
329
|
-
padding: 0;
|
|
330
|
-
font-family: 'Noto Sans SC', sans-serif;
|
|
331
|
-
background: linear-gradient(135deg, #ff6b6b 0%, #ffa502 100%);
|
|
332
|
-
display: flex;
|
|
333
|
-
justify-content: center;
|
|
334
|
-
align-items: center;
|
|
335
|
-
min-height: 100vh;
|
|
336
|
-
}
|
|
337
|
-
.container {
|
|
338
|
-
width: 500px;
|
|
339
|
-
background: white;
|
|
340
|
-
border-radius: 20px;
|
|
341
|
-
overflow: hidden;
|
|
342
|
-
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
343
|
-
}
|
|
344
|
-
.header {
|
|
345
|
-
background: linear-gradient(90deg, #ff6b6b, #ffa502);
|
|
346
|
-
padding: 20px;
|
|
347
|
-
text-align: center;
|
|
348
|
-
color: white;
|
|
349
|
-
position: relative;
|
|
350
|
-
}
|
|
351
|
-
.discount-badge {
|
|
352
|
-
position: absolute;
|
|
353
|
-
top: 15px;
|
|
354
|
-
right: 15px;
|
|
355
|
-
background: #e74c3c;
|
|
356
|
-
color: white;
|
|
357
|
-
padding: 8px 12px;
|
|
358
|
-
border-radius: 20px;
|
|
359
|
-
font-weight: 700;
|
|
360
|
-
font-size: 14px;
|
|
361
|
-
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
|
362
|
-
}
|
|
363
|
-
.new-badge {
|
|
364
|
-
position: absolute;
|
|
365
|
-
top: 15px;
|
|
366
|
-
left: 15px;
|
|
367
|
-
background: #27ae60;
|
|
368
|
-
color: white;
|
|
369
|
-
padding: 8px 12px;
|
|
370
|
-
border-radius: 20px;
|
|
371
|
-
font-weight: 700;
|
|
372
|
-
font-size: 14px;
|
|
373
|
-
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
|
374
|
-
}
|
|
375
|
-
.booth-logo {
|
|
376
|
-
font-family: 'Montserrat', sans-serif;
|
|
377
|
-
font-weight: 800;
|
|
378
|
-
font-size: 28px;
|
|
379
|
-
letter-spacing: 2px;
|
|
380
|
-
}
|
|
381
|
-
.content {
|
|
382
|
-
padding: 25px;
|
|
383
|
-
}
|
|
384
|
-
.main-image {
|
|
385
|
-
width: 100%;
|
|
386
|
-
height: 250px;
|
|
387
|
-
background: #f0f0f0 url('${item.image_url}') center/cover;
|
|
388
|
-
border-radius: 12px;
|
|
389
|
-
margin-bottom: 20px;
|
|
390
|
-
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
|
|
391
|
-
}
|
|
392
|
-
.product-title {
|
|
393
|
-
font-size: 20px;
|
|
394
|
-
margin: 0 0 15px 0;
|
|
395
|
-
color: #2c3e50;
|
|
396
|
-
font-weight: 700;
|
|
397
|
-
line-height: 1.4;
|
|
398
|
-
}
|
|
399
|
-
.author-section {
|
|
400
|
-
display: flex;
|
|
401
|
-
align-items: center;
|
|
402
|
-
gap: 12px;
|
|
403
|
-
margin-bottom: 20px;
|
|
404
|
-
padding: 12px;
|
|
405
|
-
background: #f8f9fa;
|
|
406
|
-
border-radius: 10px;
|
|
407
|
-
}
|
|
408
|
-
.author-avatar {
|
|
409
|
-
width: 50px;
|
|
410
|
-
height: 50px;
|
|
411
|
-
border-radius: 50%;
|
|
412
|
-
border: 2px solid #fff;
|
|
413
|
-
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
414
|
-
object-fit: cover;
|
|
415
|
-
}
|
|
416
|
-
.author-info {
|
|
417
|
-
flex: 1;
|
|
418
|
-
}
|
|
419
|
-
.author-name {
|
|
420
|
-
font-size: 16px;
|
|
421
|
-
font-weight: 600;
|
|
422
|
-
color: #2c3e50;
|
|
423
|
-
}
|
|
424
|
-
.price-section {
|
|
425
|
-
display: flex;
|
|
426
|
-
align-items: center;
|
|
427
|
-
justify-content: space-between;
|
|
428
|
-
margin-bottom: 15px;
|
|
429
|
-
padding: 15px;
|
|
430
|
-
background: #fff9f9;
|
|
431
|
-
border-radius: 10px;
|
|
432
|
-
border: 2px dashed #e74c3c;
|
|
433
|
-
}
|
|
434
|
-
.original-price {
|
|
435
|
-
font-size: 18px;
|
|
436
|
-
color: #7f8c8d;
|
|
437
|
-
text-decoration: line-through;
|
|
438
|
-
}
|
|
439
|
-
.current-price {
|
|
440
|
-
font-size: 24px;
|
|
441
|
-
font-weight: 700;
|
|
442
|
-
color: #e74c3c;
|
|
443
|
-
}
|
|
444
|
-
.savings {
|
|
445
|
-
text-align: center;
|
|
446
|
-
font-size: 16px;
|
|
447
|
-
color: #27ae60;
|
|
448
|
-
font-weight: 600;
|
|
449
|
-
margin-bottom: 15px;
|
|
450
|
-
}
|
|
451
|
-
.tags {
|
|
452
|
-
display: flex;
|
|
453
|
-
flex-wrap: wrap;
|
|
454
|
-
gap: 6px;
|
|
455
|
-
margin-bottom: 20px;
|
|
456
|
-
}
|
|
457
|
-
.tag {
|
|
458
|
-
background: #e1f0fa;
|
|
459
|
-
color: #3498db;
|
|
460
|
-
padding: 4px 10px;
|
|
461
|
-
border-radius: 15px;
|
|
462
|
-
font-size: 12px;
|
|
463
|
-
font-weight: 500;
|
|
464
|
-
}
|
|
465
|
-
.footer {
|
|
466
|
-
background: #2c3e50;
|
|
467
|
-
padding: 15px;
|
|
468
|
-
text-align: center;
|
|
469
|
-
color: #ecf0f1;
|
|
470
|
-
font-size: 12px;
|
|
471
|
-
}
|
|
472
|
-
.link {
|
|
473
|
-
color: #3498db;
|
|
474
|
-
text-decoration: none;
|
|
475
|
-
font-weight: 500;
|
|
476
|
-
}
|
|
477
|
-
</style>
|
|
478
|
-
</head>
|
|
479
|
-
<body>
|
|
480
|
-
<div class="container">
|
|
481
|
-
<div class="header">
|
|
482
|
-
<div class="new-badge">NEW</div>
|
|
483
|
-
<div class="discount-badge">-${item.discount_rate}%</div>
|
|
484
|
-
<div class="booth-logo">BOOTH</div>
|
|
485
|
-
<div style="margin-top: 8px; font-size: 14px;">最新折扣商品</div>
|
|
486
|
-
</div>
|
|
487
|
-
|
|
488
|
-
<div class="content">
|
|
489
|
-
<div class="main-image"></div>
|
|
490
|
-
<h1 class="product-title">${item.title}</h1>
|
|
491
|
-
|
|
492
|
-
<div class="author-section">
|
|
493
|
-
<img src="${item.author_thumbnail_url || 'https://s2.booth.pm/static-images/user/guest-32.png'}"
|
|
494
|
-
class="author-avatar"
|
|
495
|
-
alt="作者头像" onerror="this.src='https://s2.booth.pm/static-images/user/guest-32.png'">
|
|
496
|
-
<div class="author-info">
|
|
497
|
-
<div class="author-name">${item.author}</div>
|
|
498
|
-
</div>
|
|
499
|
-
</div>
|
|
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 折扣商品推送 🔥
|
|
500
323
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
</div>
|
|
509
|
-
|
|
510
|
-
${item.tags && item.tags.length > 0 ? `
|
|
511
|
-
<div class="tags">
|
|
512
|
-
${item.tags.slice(0, 5).map(tag => `<div class="tag">${tag.name}</div>`).join('')}
|
|
513
|
-
</div>
|
|
514
|
-
` : ''}
|
|
515
|
-
</div>
|
|
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}
|
|
516
331
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
</a>
|
|
523
|
-
</div>
|
|
524
|
-
</div>
|
|
525
|
-
</body>
|
|
526
|
-
</html>`;
|
|
527
|
-
}
|
|
332
|
+
📝 商品简介:
|
|
333
|
+
${(item.description || "").slice(0, 200)}${(item.description||"").length > 200 ? '...' : ''}
|
|
334
|
+
|
|
335
|
+
🔗 商品链接: https://booth.pm/zh-cn/items/${item.id}`;
|
|
336
|
+
}
|
|
528
337
|
|
|
529
338
|
async captureCardHTML(html, config) {
|
|
530
339
|
const page = await this.ctx.puppeteer.page();
|
|
531
340
|
try {
|
|
532
|
-
|
|
341
|
+
page.setDefaultTimeout(config.loadTimeout || 30000);
|
|
342
|
+
|
|
533
343
|
await page.setContent(html, {
|
|
534
|
-
waitUntil: 'networkidle0',
|
|
344
|
+
waitUntil: 'networkidle0',
|
|
535
345
|
timeout: config.loadTimeout || 30000
|
|
536
346
|
});
|
|
537
347
|
|
|
538
348
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
539
|
-
|
|
349
|
+
|
|
540
350
|
await page.setViewport({ width: 640, height: 1200 });
|
|
541
351
|
const container = await page.$('.container') || await page.$('body');
|
|
542
352
|
return await container.screenshot({
|
package/lib/discount-tracker.js
CHANGED
|
@@ -376,67 +376,49 @@ class DiscountTracker {
|
|
|
376
376
|
this.lastDiscountItems = discountItems.slice();
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
-
async sendItemsToGroups(items) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (item.has_discount_keyword) {
|
|
397
|
-
message += `🔍 折扣关键词: ${item.discount_keyword}\n`;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
message += `💰 价格:¥${(item.price||0).toLocaleString()}(原价:¥${(item.original_price||0).toLocaleString()})\n`;
|
|
401
|
-
message += `🎊 折扣:${item.discount_rate}% OFF\n`;
|
|
402
|
-
message += `💵 节省:¥${savedAmount.toLocaleString()}\n`;
|
|
403
|
-
message += `🔗 链接:${item.url}`;
|
|
404
|
-
|
|
405
|
-
for (const channelId of this.config.targetGroups) {
|
|
406
|
-
try {
|
|
407
|
-
if (channelId.includes(':')) {
|
|
408
|
-
const [platform, id] = channelId.split(':', 2);
|
|
409
|
-
for (const bot of this.ctx.bots) {
|
|
410
|
-
if (bot.platform === platform) {
|
|
411
|
-
await bot.sendMessage(id, [message, h.image(buffer, 'image/png')]);
|
|
412
|
-
break;
|
|
379
|
+
async sendItemsToGroups(items) {
|
|
380
|
+
const logger = this.ctx.logger('booth-discount');
|
|
381
|
+
const cardGenerator = require('./card-generator');
|
|
382
|
+
|
|
383
|
+
for (const item of items) {
|
|
384
|
+
try {
|
|
385
|
+
const message = cardGenerator.generateDiscountText(item);
|
|
386
|
+
|
|
387
|
+
for (const channelId of this.config.targetGroups) {
|
|
388
|
+
try {
|
|
389
|
+
if (channelId.includes(':')) {
|
|
390
|
+
const [platform, id] = channelId.split(':', 2);
|
|
391
|
+
for (const bot of this.ctx.bots) {
|
|
392
|
+
if (bot.platform === platform) {
|
|
393
|
+
await bot.sendMessage(id, message);
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
413
396
|
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
397
|
+
} else {
|
|
398
|
+
for (const bot of this.ctx.bots) {
|
|
399
|
+
try {
|
|
400
|
+
await bot.sendMessage(channelId, message);
|
|
401
|
+
break;
|
|
402
|
+
} catch (e) {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
422
405
|
}
|
|
423
406
|
}
|
|
407
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
408
|
+
} catch (e) {
|
|
409
|
+
logger.warn(`推送失败 ${channelId}: ${e.message}`);
|
|
424
410
|
}
|
|
425
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
426
|
-
} catch (e) {
|
|
427
|
-
logger.warn(`推送失败 ${channelId}: ${e.message}`);
|
|
428
411
|
}
|
|
429
|
-
}
|
|
430
412
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
413
|
+
this.recentlyPushed.set(item.id, Date.now());
|
|
414
|
+
this.discountCache.set(item.id, Date.now());
|
|
415
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
434
416
|
|
|
435
|
-
|
|
436
|
-
|
|
417
|
+
} catch (err) {
|
|
418
|
+
logger.error(`推送错误: ${err.message}`);
|
|
419
|
+
}
|
|
437
420
|
}
|
|
438
421
|
}
|
|
439
|
-
}
|
|
440
422
|
|
|
441
423
|
startDiscountTracking() {
|
|
442
424
|
const logger = this.ctx.logger('booth-discount');
|