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.
Files changed (3) hide show
  1. package/lib/index.js +292 -161
  2. package/package.json +7 -5
  3. 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
- }).description("booth-get");
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
- async function generateQRCode(id) {
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=Inter:wght@400;600&display=swap');
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: 'Inter', sans-serif;
98
- background: #f8f8f8;
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: 12px;
61
+ border-radius: 20px;
108
62
  overflow: hidden;
109
- box-shadow: 0 4px 12px rgba(0,0,0,0.08);
63
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
64
+ position: relative;
110
65
  }
111
66
  .header {
112
- padding: 18px 25px;
113
- border-bottom: 1px solid #eee;
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::after {
117
- content: "BOOTH";
73
+ .header::before {
74
+ content: "";
118
75
  position: absolute;
119
- right: 15px;
120
- top: 50%;
121
- transform: translateY(-50%) rotate(-90deg);
122
- font-size: 56px;
123
- color: #999;
124
- opacity: 0.2;
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
- position: absolute;
129
- top: 15px;
130
- left: 15px;
131
- background: rgba(255,255,255,0.95);
132
- padding: 6px 12px;
133
- border-radius: 6px;
134
- font-size: 12px;
135
- box-shadow: 0 2px 4px rgba(0,0,0,0.05);
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: 25px;
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: 8px;
145
- margin-bottom: 20px;
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: 24px;
149
- margin: 0 0 15px 0;
150
- color: #333;
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: 12px;
156
- margin-bottom: 20px;
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: 40px;
160
- height: 40px;
131
+ width: 60px;
132
+ height: 60px;
161
133
  border-radius: 50%;
162
- border: 2px solid #eee;
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: 28px;
152
+ font-size: 32px;
153
+ font-weight: 700;
166
154
  color: #e74c3c;
167
- margin-bottom: 25px;
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: #666;
171
- line-height: 1.6;
172
- padding-bottom: 20px;
173
- border-bottom: 1px solid #eee;
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: 25px;
191
+ margin-top: 30px;
192
+ border-top: 1px solid #eee;
193
+ padding-top: 25px;
177
194
  }
178
195
  .related-title {
179
- font-size: 18px;
180
- color: #444;
181
- margin-bottom: 15px;
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: #f8f8f8;
190
- border-radius: 8px;
208
+ background: white;
209
+ border-radius: 12px;
191
210
  overflow: hidden;
192
- transition: transform 0.2s;
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(-3px);
215
+ transform: translateY(-5px);
216
+ box-shadow: 0 10px 20px rgba(0,0,0,0.15);
196
217
  }
197
218
  .work-image {
198
- height: 120px;
219
+ height: 100px;
199
220
  background-size: cover;
200
221
  background-position: center;
201
222
  }
202
223
  .work-info {
203
- padding: 10px;
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: 14px;
234
+ font-size: 15px;
235
+ font-weight: 600;
207
236
  color: #e74c3c;
208
237
  }
209
238
  .footer {
210
- background: #f8f8f8;
211
- padding: 15px;
239
+ background: #2c3e50;
240
+ padding: 20px;
212
241
  text-align: center;
213
- color: #666;
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
- border-top: 1px solid #eee;
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 style="font-weight:600;">${item.author}</div>
235
- <div style="font-size:13px;color:#666;">BOOTHクリエイター</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, 200)}${item.description.length > 200 ? '...' : ''}</p>
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 style="font-size:13px;margin-bottom:6px;">${work.title.slice(0, 18)}${work.title.length > 18 ? '...' : ''}</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
- style="color:#3498db;text-decoration:none;">
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
- .action(async ({ session }, query) => {
325
- if (!query) return "请输入搜索关键词";
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
- const tags = ['3Dモデル', 'Vrchat'];
328
- const tagsParams = tags.map(tag => `tags[]=${encodeURIComponent(tag)}`).join('&');
329
- const searchUrl = `https://booth.pm/zh-cn/search/${encodeURIComponent(query)}?${tagsParams}&min_price=4500`;
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
- const page = await ctx.puppeteer.page();
332
- try {
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
- const content = await page.content();
348
- logger.debug('页面内容:', content);
349
- const regex = /item-card__wrap" id="item_(\d+)"/;
350
- const match = content.match(regex);
351
-
352
- if (!match) {
353
- logger.debug('未找到商品 ID,页面内容:', content);
354
- return "没有找到相关商品";
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
- return import_koishi.h.image(buffer, "image/png");
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('卡片生成失败:', error);
370
- return "卡片生成失败";
487
+ logger.error("搜索失败:", error);
488
+ return "搜索失败,请检查商品名称或稍后再试";
489
+ } finally {
490
+ await page.close();
371
491
  }
372
- } catch (error) {
373
- logger.error('搜索失败:', error);
374
- return "搜索失败";
375
- } finally {
376
- await page.close();
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.0.0",
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.7"
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": "^22.12.1"
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