koishi-plugin-booth-get 6.0.2 → 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 -333
- package/lib/discount-tracker.js +34 -52
- package/package.json +1 -1
package/lib/card-generator.js
CHANGED
|
@@ -315,350 +315,38 @@ class CardGenerator {
|
|
|
315
315
|
</html>`;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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>
|
|
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 折扣商品推送 🔥
|
|
603
323
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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>
|
|
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}
|
|
638
331
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
</a>
|
|
645
|
-
</div>
|
|
646
|
-
</div>
|
|
647
|
-
</body>
|
|
648
|
-
</html>\``;
|
|
649
|
-
}
|
|
332
|
+
📝 商品简介:
|
|
333
|
+
${(item.description || "").slice(0, 200)}${(item.description||"").length > 200 ? '...' : ''}
|
|
334
|
+
|
|
335
|
+
🔗 商品链接: https://booth.pm/zh-cn/items/${item.id}`;
|
|
336
|
+
}
|
|
650
337
|
|
|
651
338
|
async captureCardHTML(html, config) {
|
|
652
339
|
const page = await this.ctx.puppeteer.page();
|
|
653
340
|
try {
|
|
654
|
-
|
|
341
|
+
page.setDefaultTimeout(config.loadTimeout || 30000);
|
|
342
|
+
|
|
655
343
|
await page.setContent(html, {
|
|
656
|
-
waitUntil: 'networkidle0',
|
|
344
|
+
waitUntil: 'networkidle0',
|
|
657
345
|
timeout: config.loadTimeout || 30000
|
|
658
346
|
});
|
|
659
347
|
|
|
660
348
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
661
|
-
|
|
349
|
+
|
|
662
350
|
await page.setViewport({ width: 640, height: 1200 });
|
|
663
351
|
const container = await page.$('.container') || await page.$('body');
|
|
664
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');
|