koishi-plugin-my-pig-group-friends 1.0.0 → 1.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.
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # koishi-plugin-my-pig-group-friends
2
+
3
+ [![npm](https://img.shields.io/npm/v/koishi-plugin-my-pig-group-friends?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-my-pig-group-friends)
4
+
5
+ 猪醒 - 虚拟旅行打卡插件,让你的猪猪群友环游世界。
6
+
7
+ ## 功能特点
8
+
9
+ - **虚拟旅行打卡**:随机(或通过 LLM)生成全球旅行地点,生成精美毛玻璃效果足迹卡片
10
+ - **作息异常检测(实验性)**:自动检测用户每日首条消息,根据当地日出时间判断作息是否异常并触发旅行
11
+ - **高质量背景**:集成 Unsplash API 获取目的地真实风景图
12
+ - **AI 生图支持**:支持通过 media-luna 插件生成小猪在当地旅行的 AI 插画
13
+ - **存储集成**:深度集成 chatluna-storage-service 进行图片管理
14
+
15
+ ## 安装
16
+
17
+ ```bash
18
+ npm install koishi-plugin-my-pig-group-friends
19
+ ```
20
+
21
+ ## 依赖
22
+
23
+ ### 必需
24
+ - `database` - 存储用户状态和旅行日志
25
+ - `cron` - 定时清理和报表生成
26
+ - `puppeteer` - 生成精美卡片
27
+
28
+ ### 可选
29
+ - `chatluna` - 用于 LLM 动态生成旅行地点
30
+ - `chatluna_storage` - 用于高效管理和分发生成的图片卡片
31
+ - `media-luna` - 用于 AI 绘图功能
32
+
33
+ ## 使用方法
34
+
35
+ ### 指令
36
+
37
+ - `pig [user]` - 触发一次虚拟旅行。如果不指定用户,则对自己生效。
38
+ - `猪醒 [user]` - `pig` 指令的别名。
39
+
40
+ ## 配置项
41
+
42
+ ### 基础设置
43
+ - `outputMode`: 输出模式(image/text)。
44
+ - `travelMessageTemplate`: 旅行消息模板。
45
+
46
+ ### 地点与图片
47
+ - `llmLocationEnabled`: 是否启用 LLM 动态生成地点。
48
+ - `llmLocationModel`: 生成地点的 LLM 模型(推荐使用轻量快速模型)。
49
+ - `unsplashAccessKey`: Unsplash API 访问密钥。
50
+
51
+ ### AI 生图(可选)
52
+ - `aigcEnabled`: 是否启用 AI 绘图。
53
+ - `aigcChannel`: media-luna 绘图渠道。
54
+ - `aigcPrompt`: 绘图提示词模板。
55
+
56
+ ### 自动检测(实验性)
57
+ - `experimentalAutoDetect`: 是否启用自动作息异常检测。
58
+ - `abnormalThreshold`: 异常判定阈值(小时)。
59
+
60
+ ## License
61
+
62
+ MIT
package/lib/config.d.ts CHANGED
@@ -16,6 +16,7 @@ export interface Config {
16
16
  llmLocationModel: string;
17
17
  unsplashAccessKey: string;
18
18
  logRetentionDays: number;
19
+ experimentalAutoDetect: boolean;
19
20
  debug: boolean;
20
21
  }
21
22
  export declare const Config: Schema<Config>;
package/lib/index.js CHANGED
@@ -55,7 +55,7 @@ __name(applyDatabase, "applyDatabase");
55
55
  // src/services/sunrise.ts
56
56
  async function getSunriseInfo(ctx, lat, lng, date = "today") {
57
57
  const url = `https://api.sunrise-sunset.org/json?lat=${lat}&lng=${lng}&formatted=0&date=${date}`;
58
- const data = await ctx.http.get(url);
58
+ const data = await ctx.http.get(url, { timeout: 8e3 });
59
59
  if (data.status !== "OK") {
60
60
  throw new Error("Failed to fetch sunrise info");
61
61
  }
@@ -69,419 +69,16 @@ __name(getSunriseInfo, "getSunriseInfo");
69
69
  // src/services/card.ts
70
70
  var import_promises = require("fs/promises");
71
71
  var import_url = require("url");
72
-
73
- // src/constants.ts
74
- var LOCATIONS = [
75
- // 亚洲 (UTC+5 ~ UTC+9)
76
- {
77
- country: "Japan",
78
- countryZh: "日本",
79
- city: "Kyoto",
80
- landmark: "Fushimi Inari Shrine",
81
- landmarkZh: "伏见稻荷大社",
82
- timezone: "Asia/Tokyo",
83
- landscapeUrl: "https://images.unsplash.com/photo-1478436127897-769e1b3f0f36"
84
- },
85
- {
86
- country: "Japan",
87
- countryZh: "日本",
88
- city: "Osaka",
89
- landmark: "Dotonbori",
90
- landmarkZh: "道顿堀",
91
- timezone: "Asia/Tokyo",
92
- landscapeUrl: "https://images.unsplash.com/photo-1590559899731-a382839e5549"
93
- },
94
- {
95
- country: "South Korea",
96
- countryZh: "韩国",
97
- city: "Seoul",
98
- landmark: "Gyeongbokgung Palace",
99
- landmarkZh: "景福宫",
100
- timezone: "Asia/Seoul",
101
- landscapeUrl: "https://images.unsplash.com/photo-1534274988757-a28bf1a57c17"
102
- },
103
- {
104
- country: "China",
105
- countryZh: "中国",
106
- city: "Shanghai",
107
- landmark: "The Bund",
108
- landmarkZh: "外滩",
109
- timezone: "Asia/Shanghai",
110
- landscapeUrl: "https://images.unsplash.com/photo-1537531383496-f4749b918caa"
111
- },
112
- {
113
- country: "China",
114
- countryZh: "中国",
115
- city: "Guilin",
116
- landmark: "Li River",
117
- landmarkZh: "漓江",
118
- timezone: "Asia/Shanghai",
119
- landscapeUrl: "https://images.unsplash.com/photo-1529921879218-f99546d03a50"
120
- },
121
- {
122
- country: "Thailand",
123
- countryZh: "泰国",
124
- city: "Bangkok",
125
- landmark: "Grand Palace",
126
- landmarkZh: "大皇宫",
127
- timezone: "Asia/Bangkok",
128
- landscapeUrl: "https://images.unsplash.com/photo-1563492065599-3520f775eeed"
129
- },
130
- {
131
- country: "Vietnam",
132
- countryZh: "越南",
133
- city: "Ha Long Bay",
134
- landmark: "Ha Long Bay",
135
- landmarkZh: "下龙湾",
136
- timezone: "Asia/Ho_Chi_Minh",
137
- landscapeUrl: "https://images.unsplash.com/photo-1528127269322-539801943592"
138
- },
139
- {
140
- country: "India",
141
- countryZh: "印度",
142
- city: "Agra",
143
- landmark: "Taj Mahal",
144
- landmarkZh: "泰姬陵",
145
- timezone: "Asia/Kolkata",
146
- landscapeUrl: "https://images.unsplash.com/photo-1564507592333-c60657eea523"
147
- },
148
- {
149
- country: "Singapore",
150
- countryZh: "新加坡",
151
- city: "Singapore",
152
- landmark: "Marina Bay Sands",
153
- landmarkZh: "滨海湾金沙",
154
- timezone: "Asia/Singapore",
155
- landscapeUrl: "https://images.unsplash.com/photo-1525625293386-3f8f99389edd"
156
- },
157
- {
158
- country: "Indonesia",
159
- countryZh: "印度尼西亚",
160
- city: "Bali",
161
- landmark: "Tanah Lot Temple",
162
- landmarkZh: "海神庙",
163
- timezone: "Asia/Jakarta",
164
- landscapeUrl: "https://images.unsplash.com/photo-1537996194471-e657df975ab4"
165
- },
166
- {
167
- country: "Nepal",
168
- countryZh: "尼泊尔",
169
- city: "Kathmandu",
170
- landmark: "Himalaya Mountains",
171
- landmarkZh: "喜马拉雅山",
172
- timezone: "Asia/Kathmandu",
173
- landscapeUrl: "https://images.unsplash.com/photo-1544735716-392fe2489ffa"
174
- },
175
- {
176
- country: "UAE",
177
- countryZh: "阿联酋",
178
- city: "Dubai",
179
- landmark: "Burj Khalifa",
180
- landmarkZh: "哈利法塔",
181
- timezone: "Asia/Dubai",
182
- landscapeUrl: "https://images.unsplash.com/photo-1512453979798-5ea266f8880c"
183
- },
184
- // 欧洲 (UTC+0 ~ UTC+3)
185
- {
186
- country: "UK",
187
- countryZh: "英国",
188
- city: "London",
189
- landmark: "Big Ben",
190
- landmarkZh: "大本钟",
191
- timezone: "Europe/London",
192
- landscapeUrl: "https://images.unsplash.com/photo-1513635269975-59663e0ac1ad"
193
- },
194
- {
195
- country: "France",
196
- countryZh: "法国",
197
- city: "Provence",
198
- landmark: "Lavender Fields",
199
- landmarkZh: "薰衣草田",
200
- timezone: "Europe/Paris",
201
- landscapeUrl: "https://images.unsplash.com/photo-1499002238440-d264edd596ec"
202
- },
203
- {
204
- country: "Italy",
205
- countryZh: "意大利",
206
- city: "Venice",
207
- landmark: "Grand Canal",
208
- landmarkZh: "大运河",
209
- timezone: "Europe/Rome",
210
- landscapeUrl: "https://images.unsplash.com/photo-1514890547357-a9ee288728e0"
211
- },
212
- {
213
- country: "Italy",
214
- countryZh: "意大利",
215
- city: "Rome",
216
- landmark: "Colosseum",
217
- landmarkZh: "罗马斗兽场",
218
- timezone: "Europe/Rome",
219
- landscapeUrl: "https://images.unsplash.com/photo-1552832230-c0197dd311b5"
220
- },
221
- {
222
- country: "Spain",
223
- countryZh: "西班牙",
224
- city: "Barcelona",
225
- landmark: "Sagrada Familia",
226
- landmarkZh: "圣家堂",
227
- timezone: "Europe/Madrid",
228
- landscapeUrl: "https://images.unsplash.com/photo-1583422409516-2895a77efded"
229
- },
230
- {
231
- country: "Greece",
232
- countryZh: "希腊",
233
- city: "Santorini",
234
- landmark: "Oia Village",
235
- landmarkZh: "伊亚小镇",
236
- timezone: "Europe/Athens",
237
- landscapeUrl: "https://images.unsplash.com/photo-1613395877344-13d4a8e0d49e"
238
- },
239
- {
240
- country: "Netherlands",
241
- countryZh: "荷兰",
242
- city: "Amsterdam",
243
- landmark: "Canal Ring",
244
- landmarkZh: "运河环",
245
- timezone: "Europe/Amsterdam",
246
- landscapeUrl: "https://images.unsplash.com/photo-1534351590666-13e3e96b5017"
247
- },
248
- {
249
- country: "Switzerland",
250
- countryZh: "瑞士",
251
- city: "Interlaken",
252
- landmark: "Swiss Alps",
253
- landmarkZh: "阿尔卑斯山",
254
- timezone: "Europe/Zurich",
255
- landscapeUrl: "https://images.unsplash.com/photo-1531366936337-7c912a4589a7"
256
- },
257
- {
258
- country: "Norway",
259
- countryZh: "挪威",
260
- city: "Tromsø",
261
- landmark: "Northern Lights",
262
- landmarkZh: "北极光",
263
- timezone: "Europe/Oslo",
264
- landscapeUrl: "https://images.unsplash.com/photo-1483347756197-71ef80e95f73"
265
- },
266
- {
267
- country: "Iceland",
268
- countryZh: "冰岛",
269
- city: "Reykjavik",
270
- landmark: "Blue Lagoon",
271
- landmarkZh: "蓝湖温泉",
272
- timezone: "Atlantic/Reykjavik",
273
- landscapeUrl: "https://images.unsplash.com/photo-1504829857797-ddff29c27927"
274
- },
275
- {
276
- country: "Turkey",
277
- countryZh: "土耳其",
278
- city: "Cappadocia",
279
- landmark: "Hot Air Balloons",
280
- landmarkZh: "热气球",
281
- timezone: "Europe/Istanbul",
282
- landscapeUrl: "https://images.unsplash.com/photo-1641128324972-af3212f0f6bd"
283
- },
284
- {
285
- country: "Russia",
286
- countryZh: "俄罗斯",
287
- city: "Moscow",
288
- landmark: "Red Square",
289
- landmarkZh: "红场",
290
- timezone: "Europe/Moscow",
291
- landscapeUrl: "https://images.unsplash.com/photo-1513326738677-b964603b136d"
292
- },
293
- // 非洲 (UTC+0 ~ UTC+3)
294
- {
295
- country: "Egypt",
296
- countryZh: "埃及",
297
- city: "Giza",
298
- landmark: "Pyramids of Giza",
299
- landmarkZh: "吉萨金字塔",
300
- timezone: "Africa/Cairo",
301
- landscapeUrl: "https://images.unsplash.com/photo-1503177119275-0aa32b3a9368"
302
- },
303
- {
304
- country: "Morocco",
305
- countryZh: "摩洛哥",
306
- city: "Marrakech",
307
- landmark: "Jardin Majorelle",
308
- landmarkZh: "马约尔花园",
309
- timezone: "Africa/Casablanca",
310
- landscapeUrl: "https://images.unsplash.com/photo-1489749798305-4fea3ae63d43"
311
- },
312
- {
313
- country: "South Africa",
314
- countryZh: "南非",
315
- city: "Cape Town",
316
- landmark: "Table Mountain",
317
- landmarkZh: "桌山",
318
- timezone: "Africa/Johannesburg",
319
- landscapeUrl: "https://images.unsplash.com/photo-1580060839134-75a5edca2e99"
320
- },
321
- {
322
- country: "Kenya",
323
- countryZh: "肯尼亚",
324
- city: "Masai Mara",
325
- landmark: "Safari Savanna",
326
- landmarkZh: "马赛马拉草原",
327
- timezone: "Africa/Nairobi",
328
- landscapeUrl: "https://images.unsplash.com/photo-1547970810-dc1eac37d174"
329
- },
330
- // 北美洲 (UTC-5 ~ UTC-8)
331
- {
332
- country: "USA",
333
- countryZh: "美国",
334
- city: "New York",
335
- landmark: "Times Square",
336
- landmarkZh: "时代广场",
337
- timezone: "America/New_York",
338
- landscapeUrl: "https://images.unsplash.com/photo-1534430480872-3498386e7856"
339
- },
340
- {
341
- country: "USA",
342
- countryZh: "美国",
343
- city: "San Francisco",
344
- landmark: "Golden Gate Bridge",
345
- landmarkZh: "金门大桥",
346
- timezone: "America/Los_Angeles",
347
- landscapeUrl: "https://images.unsplash.com/photo-1449034446853-66c86144b0ad"
348
- },
349
- {
350
- country: "USA",
351
- countryZh: "美国",
352
- city: "Las Vegas",
353
- landmark: "The Strip",
354
- landmarkZh: "拉斯维加斯大道",
355
- timezone: "America/Los_Angeles",
356
- landscapeUrl: "https://images.unsplash.com/photo-1605833556294-ea5c7a74f57d"
357
- },
358
- {
359
- country: "USA",
360
- countryZh: "美国",
361
- city: "Arizona",
362
- landmark: "Grand Canyon",
363
- landmarkZh: "大峡谷",
364
- timezone: "America/Phoenix",
365
- landscapeUrl: "https://images.unsplash.com/photo-1474044159687-1ee9f3a51722"
366
- },
367
- {
368
- country: "Canada",
369
- countryZh: "加拿大",
370
- city: "Banff",
371
- landmark: "Lake Louise",
372
- landmarkZh: "路易斯湖",
373
- timezone: "America/Edmonton",
374
- landscapeUrl: "https://images.unsplash.com/photo-1502085671122-2d218cd434e6"
375
- },
376
- {
377
- country: "Canada",
378
- countryZh: "加拿大",
379
- city: "Niagara Falls",
380
- landmark: "Niagara Falls",
381
- landmarkZh: "尼亚加拉瀑布",
382
- timezone: "America/Toronto",
383
- landscapeUrl: "https://images.unsplash.com/photo-1489447068241-b3490214e879"
384
- },
385
- {
386
- country: "Mexico",
387
- countryZh: "墨西哥",
388
- city: "Cancun",
389
- landmark: "Chichen Itza",
390
- landmarkZh: "奇琴伊察",
391
- timezone: "America/Cancun",
392
- landscapeUrl: "https://images.unsplash.com/photo-1518638150340-f706e86654de"
393
- },
394
- // 南美洲 (UTC-3 ~ UTC-5)
395
- {
396
- country: "Brazil",
397
- countryZh: "巴西",
398
- city: "Rio de Janeiro",
399
- landmark: "Christ the Redeemer",
400
- landmarkZh: "基督救世主像",
401
- timezone: "America/Sao_Paulo",
402
- landscapeUrl: "https://images.unsplash.com/photo-1483729558449-99ef09a8c325"
403
- },
404
- {
405
- country: "Peru",
406
- countryZh: "秘鲁",
407
- city: "Cusco",
408
- landmark: "Machu Picchu",
409
- landmarkZh: "马丘比丘",
410
- timezone: "America/Lima",
411
- landscapeUrl: "https://images.unsplash.com/photo-1526392060635-9d6019884377"
412
- },
413
- {
414
- country: "Argentina",
415
- countryZh: "阿根廷",
416
- city: "Patagonia",
417
- landmark: "Perito Moreno Glacier",
418
- landmarkZh: "莫雷诺冰川",
419
- timezone: "America/Argentina/Buenos_Aires",
420
- landscapeUrl: "https://images.unsplash.com/photo-1551279880-03041531948f"
421
- },
422
- {
423
- country: "Chile",
424
- countryZh: "智利",
425
- city: "Atacama",
426
- landmark: "Atacama Desert",
427
- landmarkZh: "阿塔卡马沙漠",
428
- timezone: "America/Santiago",
429
- landscapeUrl: "https://images.unsplash.com/photo-1489392191049-fc10c97e64b6"
430
- },
431
- // 大洋洲 (UTC+10 ~ UTC+12)
432
- {
433
- country: "Australia",
434
- countryZh: "澳大利亚",
435
- city: "Sydney",
436
- landmark: "Sydney Opera House",
437
- landmarkZh: "悉尼歌剧院",
438
- timezone: "Australia/Sydney",
439
- landscapeUrl: "https://images.unsplash.com/photo-1506973035872-a4ec16b8e8d9"
440
- },
441
- {
442
- country: "Australia",
443
- countryZh: "澳大利亚",
444
- city: "Queensland",
445
- landmark: "Great Barrier Reef",
446
- landmarkZh: "大堡礁",
447
- timezone: "Australia/Brisbane",
448
- landscapeUrl: "https://images.unsplash.com/photo-1559128010-7c1ad6e1b6a5"
449
- },
450
- {
451
- country: "New Zealand",
452
- countryZh: "新西兰",
453
- city: "Queenstown",
454
- landmark: "Milford Sound",
455
- landmarkZh: "米尔福德峡湾",
456
- timezone: "Pacific/Auckland",
457
- landscapeUrl: "https://images.unsplash.com/photo-1507699622108-4be3abd695ad"
458
- },
459
- {
460
- country: "New Zealand",
461
- countryZh: "新西兰",
462
- city: "Matamata",
463
- landmark: "Hobbiton",
464
- landmarkZh: "霍比屯",
465
- timezone: "Pacific/Auckland",
466
- landscapeUrl: "https://images.unsplash.com/photo-1507097634215-e82e6b552cae"
467
- },
468
- {
469
- country: "Fiji",
470
- countryZh: "斐济",
471
- city: "Nadi",
472
- landmark: "Fiji Islands",
473
- landmarkZh: "斐济群岛",
474
- timezone: "Pacific/Fiji",
475
- landscapeUrl: "https://images.unsplash.com/photo-1589179899031-d86b7e3fcd61"
476
- }
477
- ];
478
-
479
- // src/services/card.ts
480
72
  async function generateFootprintCard(ctx, config, data, userInfo, platform, backgroundUrl) {
481
73
  const username = userInfo.username || userInfo.userId;
482
74
  let avatarUrl = userInfo.avatarUrl || "";
483
75
  if (!avatarUrl) {
484
- avatarUrl = `https://ui-avatars.com/api/?name=${encodeURIComponent(username)}&background=random&size=200`;
76
+ if (platform === "onebot") {
77
+ avatarUrl = `https://q.qlogo.cn/headimg_dl?dst_uin=${userInfo.userId}&spec=640`;
78
+ } else {
79
+ const initial = username.charAt(0) || "U";
80
+ avatarUrl = `https://ui-avatars.com/api/?name=${encodeURIComponent(initial)}&background=007AFF&color=fff&size=200&bold=true`;
81
+ }
485
82
  }
486
83
  let bgImage = backgroundUrl || data.location.landscapeUrl;
487
84
  if (config.debug) ctx.logger("pig").debug(`Initial background URL: ${bgImage || "none"}`);
@@ -529,7 +126,8 @@ async function generateFootprintCard(ctx, config, data, userInfo, platform, back
529
126
  if (config.debug) ctx.logger("pig").debug(`Server-side fetching background: ${normalized}`);
530
127
  const response = await ctx.http(normalized, {
531
128
  responseType: "arraybuffer",
532
- timeout: 15e3,
129
+ timeout: 8e3,
130
+ // Reduced from 15s to 8s for better responsiveness
533
131
  headers: {
534
132
  "User-Agent": "Mozilla/5.0",
535
133
  "Accept": "image/avif,image/webp,image/apng,image/*,*/*;q=0.8"
@@ -571,20 +169,7 @@ async function generateFootprintCard(ctx, config, data, userInfo, platform, back
571
169
  ctx.logger("pig").debug(`Successfully converted background to base64 (length=${fetched.length})`);
572
170
  }
573
171
  } else {
574
- if (config.debug) ctx.logger("pig").warn("Background fetch failed, trying static fallback");
575
- const fallbackLoc = LOCATIONS.find(
576
- (l) => l.country === data.location.country || l.timezone === data.location.timezone
577
- );
578
- if (fallbackLoc && fallbackLoc.landscapeUrl !== bgImage) {
579
- if (config.debug) ctx.logger("pig").info(`Found fallback static location: ${fallbackLoc.landmark} (${fallbackLoc.landscapeUrl})`);
580
- const fallback = await fetchToDataUrl(fallbackLoc.landscapeUrl);
581
- if (fallback) {
582
- bgImage = fallback;
583
- if (config.debug) ctx.logger("pig").info("Successfully used fallback static image");
584
- } else if (config.debug) {
585
- ctx.logger("pig").warn("Fallback static image also failed");
586
- }
587
- }
172
+ if (config.debug) ctx.logger("pig").warn("Background fetch failed, proceeding with URL directly (no fallback fetch)");
588
173
  }
589
174
  if (config.debug) {
590
175
  ctx.logger("pig").debug(`Final background value: ${bgImage ? bgImage.startsWith("data:") ? `data-url(${bgImage.length})` : bgImage : "none"}`);
@@ -598,8 +183,7 @@ async function generateFootprintCard(ctx, config, data, userInfo, platform, back
598
183
  <head>
599
184
  <meta charset="UTF-8">
600
185
  <style>
601
- @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&display=swap');
602
-
186
+ /* System fonts only - no external font loading for Docker compatibility */
603
187
  * {
604
188
  margin: 0;
605
189
  padding: 0;
@@ -610,7 +194,8 @@ async function generateFootprintCard(ctx, config, data, userInfo, platform, back
610
194
  width: 1080px;
611
195
  height: 1920px;
612
196
  overflow: hidden;
613
- font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif;
197
+ /* Prioritize system fonts with wide Unicode coverage for emoji and CJK support */
198
+ font-family: "Noto Sans CJK SC", "Noto Sans SC", "Source Han Sans SC", "Microsoft YaHei", "WenQuanYi Micro Hei", "Droid Sans Fallback", "PingFang SC", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji";
614
199
  background: #f0f0f2;
615
200
  --bg-image: url('${bgImage}');
616
201
  }
@@ -689,11 +274,11 @@ async function generateFootprintCard(ctx, config, data, userInfo, platform, back
689
274
  content: "";
690
275
  position: absolute;
691
276
  inset: 0;
692
- /* 关键:使用 fixed attachment 模拟透视背景 */
277
+ /* Use scroll instead of fixed to avoid Puppeteer rendering issues */
693
278
  background-image: var(--bg-image);
694
- background-attachment: fixed;
279
+ background-attachment: scroll;
695
280
  background-size: cover;
696
- background-position: center;
281
+ background-position: center bottom;
697
282
 
698
283
  /* 模糊处理,模拟毛玻璃内部的散射 */
699
284
  filter: blur(25px) brightness(1.1);
@@ -935,11 +520,17 @@ async function generateFootprintCard(ctx, config, data, userInfo, platform, back
935
520
  for (let i = 0; i < allElements.length; i++) {
936
521
  const bg = window.getComputedStyle(allElements[i]).backgroundImage;
937
522
  if (bg && bg !== 'none' && bg.includes('url(')) {
938
- const urlMatch = bg.match(/url(["']?(.+?)["']?)/);
939
- if (urlMatch) {
940
- console.log('Found background image: ' + urlMatch[1]);
523
+ const urlMatch = bg.match(/url(s*?["']?(.+?)["']?s*?)/);
524
+ if (urlMatch && urlMatch[1]) {
525
+ const url = urlMatch[1];
526
+ // Skip data URLs as they don't need pre-loading and can be fragile in Image constructor
527
+ if (url.startsWith('data:')) {
528
+ console.log('Skipping data-url pre-load');
529
+ continue;
530
+ }
531
+ console.log('Found background image: ' + url);
941
532
  const img = new Image();
942
- img.src = urlMatch[1];
533
+ img.src = url;
943
534
  bgImages.push(img);
944
535
  }
945
536
  }
@@ -970,16 +561,17 @@ async function generateFootprintCard(ctx, config, data, userInfo, platform, back
970
561
  ]);
971
562
 
972
563
  console.log('All images and fonts loaded');
973
- // Extra safety delay for rendering
974
- await new Promise(r => setTimeout(r, 400));
564
+ // Extra safety delay for rendering (reduced from 400ms)
565
+ await new Promise(r => setTimeout(r, 200));
975
566
  }
976
567
  window.renderReady = waitForImages();
977
568
  </script>
978
569
  </body>
979
570
  </html>
980
571
  `;
572
+ let page = null;
981
573
  try {
982
- const page = await ctx.puppeteer.page();
574
+ page = await ctx.puppeteer.page();
983
575
  if (config.debug) {
984
576
  page.on("console", (msg) => ctx.logger("pig").debug(`[Browser] ${msg.text()}`));
985
577
  page.on("requestfailed", (request) => {
@@ -987,30 +579,445 @@ async function generateFootprintCard(ctx, config, data, userInfo, platform, back
987
579
  });
988
580
  }
989
581
  await page.setViewport({ width: 1080, height: 1920, deviceScaleFactor: 1 });
990
- await page.setContent(html, { waitUntil: "networkidle0" });
582
+ await page.setContent(html, { waitUntil: "domcontentloaded" });
991
583
  await page.evaluate(() => window["renderReady"]);
992
584
  const buffer = await page.screenshot({ type: "png", fullPage: true });
993
- await page.close();
994
585
  const filename = `pig_${userInfo.userId}_${now.getTime()}.png`;
995
586
  ctx.logger("pig").info(`足迹卡片已生成: ${filename}`);
996
587
  return { buffer, filename };
997
588
  } catch (e) {
998
589
  ctx.logger("pig").error("Failed to generate card", e);
999
590
  throw e;
591
+ } finally {
592
+ if (page) {
593
+ try {
594
+ await page.close();
595
+ } catch (closeError) {
596
+ if (config.debug) {
597
+ ctx.logger("pig").warn(`Failed to close puppeteer page: ${closeError}`);
598
+ }
599
+ }
600
+ }
601
+ }
602
+ }
603
+ __name(generateFootprintCard, "generateFootprintCard");
604
+ function escapeHtml(text) {
605
+ const map = {
606
+ "&": "&amp;",
607
+ "<": "&lt;",
608
+ ">": "&gt;",
609
+ '"': "&quot;",
610
+ "'": "&#039;"
611
+ };
612
+ return text.replace(/[&<>"']/g, (m) => map[m]);
613
+ }
614
+ __name(escapeHtml, "escapeHtml");
615
+
616
+ // src/constants.ts
617
+ var LOCATIONS = [
618
+ // 亚洲 (UTC+5 ~ UTC+9)
619
+ {
620
+ country: "Japan",
621
+ countryZh: "日本",
622
+ city: "Kyoto",
623
+ landmark: "Fushimi Inari Shrine",
624
+ landmarkZh: "伏见稻荷大社",
625
+ timezone: "Asia/Tokyo",
626
+ landscapeUrl: "https://images.unsplash.com/photo-1478436127897-769e1b3f0f36"
627
+ },
628
+ {
629
+ country: "Japan",
630
+ countryZh: "日本",
631
+ city: "Osaka",
632
+ landmark: "Dotonbori",
633
+ landmarkZh: "道顿堀",
634
+ timezone: "Asia/Tokyo",
635
+ landscapeUrl: "https://images.unsplash.com/photo-1590559899731-a382839e5549"
636
+ },
637
+ {
638
+ country: "South Korea",
639
+ countryZh: "韩国",
640
+ city: "Seoul",
641
+ landmark: "Gyeongbokgung Palace",
642
+ landmarkZh: "景福宫",
643
+ timezone: "Asia/Seoul",
644
+ landscapeUrl: "https://images.unsplash.com/photo-1534274988757-a28bf1a57c17"
645
+ },
646
+ {
647
+ country: "China",
648
+ countryZh: "中国",
649
+ city: "Shanghai",
650
+ landmark: "The Bund",
651
+ landmarkZh: "外滩",
652
+ timezone: "Asia/Shanghai",
653
+ landscapeUrl: "https://images.unsplash.com/photo-1537531383496-f4749b918caa"
654
+ },
655
+ {
656
+ country: "China",
657
+ countryZh: "中国",
658
+ city: "Guilin",
659
+ landmark: "Li River",
660
+ landmarkZh: "漓江",
661
+ timezone: "Asia/Shanghai",
662
+ landscapeUrl: "https://images.unsplash.com/photo-1529921879218-f99546d03a50"
663
+ },
664
+ {
665
+ country: "Thailand",
666
+ countryZh: "泰国",
667
+ city: "Bangkok",
668
+ landmark: "Grand Palace",
669
+ landmarkZh: "大皇宫",
670
+ timezone: "Asia/Bangkok",
671
+ landscapeUrl: "https://images.unsplash.com/photo-1563492065599-3520f775eeed"
672
+ },
673
+ {
674
+ country: "Vietnam",
675
+ countryZh: "越南",
676
+ city: "Ha Long Bay",
677
+ landmark: "Ha Long Bay",
678
+ landmarkZh: "下龙湾",
679
+ timezone: "Asia/Ho_Chi_Minh",
680
+ landscapeUrl: "https://images.unsplash.com/photo-1528127269322-539801943592"
681
+ },
682
+ {
683
+ country: "India",
684
+ countryZh: "印度",
685
+ city: "Agra",
686
+ landmark: "Taj Mahal",
687
+ landmarkZh: "泰姬陵",
688
+ timezone: "Asia/Kolkata",
689
+ landscapeUrl: "https://images.unsplash.com/photo-1564507592333-c60657eea523"
690
+ },
691
+ {
692
+ country: "Singapore",
693
+ countryZh: "新加坡",
694
+ city: "Singapore",
695
+ landmark: "Marina Bay Sands",
696
+ landmarkZh: "滨海湾金沙",
697
+ timezone: "Asia/Singapore",
698
+ landscapeUrl: "https://images.unsplash.com/photo-1525625293386-3f8f99389edd"
699
+ },
700
+ {
701
+ country: "Indonesia",
702
+ countryZh: "印度尼西亚",
703
+ city: "Bali",
704
+ landmark: "Tanah Lot Temple",
705
+ landmarkZh: "海神庙",
706
+ timezone: "Asia/Jakarta",
707
+ landscapeUrl: "https://images.unsplash.com/photo-1537996194471-e657df975ab4"
708
+ },
709
+ {
710
+ country: "Nepal",
711
+ countryZh: "尼泊尔",
712
+ city: "Kathmandu",
713
+ landmark: "Himalaya Mountains",
714
+ landmarkZh: "喜马拉雅山",
715
+ timezone: "Asia/Kathmandu",
716
+ landscapeUrl: "https://images.unsplash.com/photo-1544735716-392fe2489ffa"
717
+ },
718
+ {
719
+ country: "UAE",
720
+ countryZh: "阿联酋",
721
+ city: "Dubai",
722
+ landmark: "Burj Khalifa",
723
+ landmarkZh: "哈利法塔",
724
+ timezone: "Asia/Dubai",
725
+ landscapeUrl: "https://images.unsplash.com/photo-1512453979798-5ea266f8880c"
726
+ },
727
+ // 欧洲 (UTC+0 ~ UTC+3)
728
+ {
729
+ country: "UK",
730
+ countryZh: "英国",
731
+ city: "London",
732
+ landmark: "Big Ben",
733
+ landmarkZh: "大本钟",
734
+ timezone: "Europe/London",
735
+ landscapeUrl: "https://images.unsplash.com/photo-1513635269975-59663e0ac1ad"
736
+ },
737
+ {
738
+ country: "France",
739
+ countryZh: "法国",
740
+ city: "Provence",
741
+ landmark: "Lavender Fields",
742
+ landmarkZh: "薰衣草田",
743
+ timezone: "Europe/Paris",
744
+ landscapeUrl: "https://images.unsplash.com/photo-1499002238440-d264edd596ec"
745
+ },
746
+ {
747
+ country: "Italy",
748
+ countryZh: "意大利",
749
+ city: "Venice",
750
+ landmark: "Grand Canal",
751
+ landmarkZh: "大运河",
752
+ timezone: "Europe/Rome",
753
+ landscapeUrl: "https://images.unsplash.com/photo-1514890547357-a9ee288728e0"
754
+ },
755
+ {
756
+ country: "Italy",
757
+ countryZh: "意大利",
758
+ city: "Rome",
759
+ landmark: "Colosseum",
760
+ landmarkZh: "罗马斗兽场",
761
+ timezone: "Europe/Rome",
762
+ landscapeUrl: "https://images.unsplash.com/photo-1552832230-c0197dd311b5"
763
+ },
764
+ {
765
+ country: "Spain",
766
+ countryZh: "西班牙",
767
+ city: "Barcelona",
768
+ landmark: "Sagrada Familia",
769
+ landmarkZh: "圣家堂",
770
+ timezone: "Europe/Madrid",
771
+ landscapeUrl: "https://images.unsplash.com/photo-1583422409516-2895a77efded"
772
+ },
773
+ {
774
+ country: "Greece",
775
+ countryZh: "希腊",
776
+ city: "Santorini",
777
+ landmark: "Oia Village",
778
+ landmarkZh: "伊亚小镇",
779
+ timezone: "Europe/Athens",
780
+ landscapeUrl: "https://images.unsplash.com/photo-1613395877344-13d4a8e0d49e"
781
+ },
782
+ {
783
+ country: "Netherlands",
784
+ countryZh: "荷兰",
785
+ city: "Amsterdam",
786
+ landmark: "Canal Ring",
787
+ landmarkZh: "运河环",
788
+ timezone: "Europe/Amsterdam",
789
+ landscapeUrl: "https://images.unsplash.com/photo-1534351590666-13e3e96b5017"
790
+ },
791
+ {
792
+ country: "Switzerland",
793
+ countryZh: "瑞士",
794
+ city: "Interlaken",
795
+ landmark: "Swiss Alps",
796
+ landmarkZh: "阿尔卑斯山",
797
+ timezone: "Europe/Zurich",
798
+ landscapeUrl: "https://images.unsplash.com/photo-1531366936337-7c912a4589a7"
799
+ },
800
+ {
801
+ country: "Norway",
802
+ countryZh: "挪威",
803
+ city: "Tromsø",
804
+ landmark: "Northern Lights",
805
+ landmarkZh: "北极光",
806
+ timezone: "Europe/Oslo",
807
+ landscapeUrl: "https://images.unsplash.com/photo-1483347756197-71ef80e95f73"
808
+ },
809
+ {
810
+ country: "Iceland",
811
+ countryZh: "冰岛",
812
+ city: "Reykjavik",
813
+ landmark: "Blue Lagoon",
814
+ landmarkZh: "蓝湖温泉",
815
+ timezone: "Atlantic/Reykjavik",
816
+ landscapeUrl: "https://images.unsplash.com/photo-1504829857797-ddff29c27927"
817
+ },
818
+ {
819
+ country: "Turkey",
820
+ countryZh: "土耳其",
821
+ city: "Cappadocia",
822
+ landmark: "Hot Air Balloons",
823
+ landmarkZh: "热气球",
824
+ timezone: "Europe/Istanbul",
825
+ landscapeUrl: "https://images.unsplash.com/photo-1641128324972-af3212f0f6bd"
826
+ },
827
+ {
828
+ country: "Russia",
829
+ countryZh: "俄罗斯",
830
+ city: "Moscow",
831
+ landmark: "Red Square",
832
+ landmarkZh: "红场",
833
+ timezone: "Europe/Moscow",
834
+ landscapeUrl: "https://images.unsplash.com/photo-1513326738677-b964603b136d"
835
+ },
836
+ // 非洲 (UTC+0 ~ UTC+3)
837
+ {
838
+ country: "Egypt",
839
+ countryZh: "埃及",
840
+ city: "Giza",
841
+ landmark: "Pyramids of Giza",
842
+ landmarkZh: "吉萨金字塔",
843
+ timezone: "Africa/Cairo",
844
+ landscapeUrl: "https://images.unsplash.com/photo-1503177119275-0aa32b3a9368"
845
+ },
846
+ {
847
+ country: "Morocco",
848
+ countryZh: "摩洛哥",
849
+ city: "Marrakech",
850
+ landmark: "Jardin Majorelle",
851
+ landmarkZh: "马约尔花园",
852
+ timezone: "Africa/Casablanca",
853
+ landscapeUrl: "https://images.unsplash.com/photo-1489749798305-4fea3ae63d43"
854
+ },
855
+ {
856
+ country: "South Africa",
857
+ countryZh: "南非",
858
+ city: "Cape Town",
859
+ landmark: "Table Mountain",
860
+ landmarkZh: "桌山",
861
+ timezone: "Africa/Johannesburg",
862
+ landscapeUrl: "https://images.unsplash.com/photo-1580060839134-75a5edca2e99"
863
+ },
864
+ {
865
+ country: "Kenya",
866
+ countryZh: "肯尼亚",
867
+ city: "Masai Mara",
868
+ landmark: "Safari Savanna",
869
+ landmarkZh: "马赛马拉草原",
870
+ timezone: "Africa/Nairobi",
871
+ landscapeUrl: "https://images.unsplash.com/photo-1547970810-dc1eac37d174"
872
+ },
873
+ // 北美洲 (UTC-5 ~ UTC-8)
874
+ {
875
+ country: "USA",
876
+ countryZh: "美国",
877
+ city: "New York",
878
+ landmark: "Times Square",
879
+ landmarkZh: "时代广场",
880
+ timezone: "America/New_York",
881
+ landscapeUrl: "https://images.unsplash.com/photo-1534430480872-3498386e7856"
882
+ },
883
+ {
884
+ country: "USA",
885
+ countryZh: "美国",
886
+ city: "San Francisco",
887
+ landmark: "Golden Gate Bridge",
888
+ landmarkZh: "金门大桥",
889
+ timezone: "America/Los_Angeles",
890
+ landscapeUrl: "https://images.unsplash.com/photo-1449034446853-66c86144b0ad"
891
+ },
892
+ {
893
+ country: "USA",
894
+ countryZh: "美国",
895
+ city: "Las Vegas",
896
+ landmark: "The Strip",
897
+ landmarkZh: "拉斯维加斯大道",
898
+ timezone: "America/Los_Angeles",
899
+ landscapeUrl: "https://images.unsplash.com/photo-1605833556294-ea5c7a74f57d"
900
+ },
901
+ {
902
+ country: "USA",
903
+ countryZh: "美国",
904
+ city: "Arizona",
905
+ landmark: "Grand Canyon",
906
+ landmarkZh: "大峡谷",
907
+ timezone: "America/Phoenix",
908
+ landscapeUrl: "https://images.unsplash.com/photo-1474044159687-1ee9f3a51722"
909
+ },
910
+ {
911
+ country: "Canada",
912
+ countryZh: "加拿大",
913
+ city: "Banff",
914
+ landmark: "Lake Louise",
915
+ landmarkZh: "路易斯湖",
916
+ timezone: "America/Edmonton",
917
+ landscapeUrl: "https://images.unsplash.com/photo-1502085671122-2d218cd434e6"
918
+ },
919
+ {
920
+ country: "Canada",
921
+ countryZh: "加拿大",
922
+ city: "Niagara Falls",
923
+ landmark: "Niagara Falls",
924
+ landmarkZh: "尼亚加拉瀑布",
925
+ timezone: "America/Toronto",
926
+ landscapeUrl: "https://images.unsplash.com/photo-1489447068241-b3490214e879"
927
+ },
928
+ {
929
+ country: "Mexico",
930
+ countryZh: "墨西哥",
931
+ city: "Cancun",
932
+ landmark: "Chichen Itza",
933
+ landmarkZh: "奇琴伊察",
934
+ timezone: "America/Cancun",
935
+ landscapeUrl: "https://images.unsplash.com/photo-1518638150340-f706e86654de"
936
+ },
937
+ // 南美洲 (UTC-3 ~ UTC-5)
938
+ {
939
+ country: "Brazil",
940
+ countryZh: "巴西",
941
+ city: "Rio de Janeiro",
942
+ landmark: "Christ the Redeemer",
943
+ landmarkZh: "基督救世主像",
944
+ timezone: "America/Sao_Paulo",
945
+ landscapeUrl: "https://images.unsplash.com/photo-1483729558449-99ef09a8c325"
946
+ },
947
+ {
948
+ country: "Peru",
949
+ countryZh: "秘鲁",
950
+ city: "Cusco",
951
+ landmark: "Machu Picchu",
952
+ landmarkZh: "马丘比丘",
953
+ timezone: "America/Lima",
954
+ landscapeUrl: "https://images.unsplash.com/photo-1526392060635-9d6019884377"
955
+ },
956
+ {
957
+ country: "Argentina",
958
+ countryZh: "阿根廷",
959
+ city: "Patagonia",
960
+ landmark: "Perito Moreno Glacier",
961
+ landmarkZh: "莫雷诺冰川",
962
+ timezone: "America/Argentina/Buenos_Aires",
963
+ landscapeUrl: "https://images.unsplash.com/photo-1551279880-03041531948f"
964
+ },
965
+ {
966
+ country: "Chile",
967
+ countryZh: "智利",
968
+ city: "Atacama",
969
+ landmark: "Atacama Desert",
970
+ landmarkZh: "阿塔卡马沙漠",
971
+ timezone: "America/Santiago",
972
+ landscapeUrl: "https://images.unsplash.com/photo-1489392191049-fc10c97e64b6"
973
+ },
974
+ // 大洋洲 (UTC+10 ~ UTC+12)
975
+ {
976
+ country: "Australia",
977
+ countryZh: "澳大利亚",
978
+ city: "Sydney",
979
+ landmark: "Sydney Opera House",
980
+ landmarkZh: "悉尼歌剧院",
981
+ timezone: "Australia/Sydney",
982
+ landscapeUrl: "https://images.unsplash.com/photo-1506973035872-a4ec16b8e8d9"
983
+ },
984
+ {
985
+ country: "Australia",
986
+ countryZh: "澳大利亚",
987
+ city: "Queensland",
988
+ landmark: "Great Barrier Reef",
989
+ landmarkZh: "大堡礁",
990
+ timezone: "Australia/Brisbane",
991
+ landscapeUrl: "https://images.unsplash.com/photo-1559128010-7c1ad6e1b6a5"
992
+ },
993
+ {
994
+ country: "New Zealand",
995
+ countryZh: "新西兰",
996
+ city: "Queenstown",
997
+ landmark: "Milford Sound",
998
+ landmarkZh: "米尔福德峡湾",
999
+ timezone: "Pacific/Auckland",
1000
+ landscapeUrl: "https://images.unsplash.com/photo-1507699622108-4be3abd695ad"
1001
+ },
1002
+ {
1003
+ country: "New Zealand",
1004
+ countryZh: "新西兰",
1005
+ city: "Matamata",
1006
+ landmark: "Hobbiton",
1007
+ landmarkZh: "霍比屯",
1008
+ timezone: "Pacific/Auckland",
1009
+ landscapeUrl: "https://images.unsplash.com/photo-1507097634215-e82e6b552cae"
1010
+ },
1011
+ {
1012
+ country: "Fiji",
1013
+ countryZh: "斐济",
1014
+ city: "Nadi",
1015
+ landmark: "Fiji Islands",
1016
+ landmarkZh: "斐济群岛",
1017
+ timezone: "Pacific/Fiji",
1018
+ landscapeUrl: "https://images.unsplash.com/photo-1589179899031-d86b7e3fcd61"
1000
1019
  }
1001
- }
1002
- __name(generateFootprintCard, "generateFootprintCard");
1003
- function escapeHtml(text) {
1004
- const map = {
1005
- "&": "&amp;",
1006
- "<": "&lt;",
1007
- ">": "&gt;",
1008
- '"': "&quot;",
1009
- "'": "&#039;"
1010
- };
1011
- return text.replace(/[&<>"']/g, (m) => map[m]);
1012
- }
1013
- __name(escapeHtml, "escapeHtml");
1020
+ ];
1014
1021
 
1015
1022
  // src/services/location.ts
1016
1023
  var import_messages = require("@langchain/core/messages");
@@ -1040,7 +1047,12 @@ async function searchUnsplashPhoto(ctx, accessKey, query, debug = false) {
1040
1047
  }
1041
1048
  );
1042
1049
  if (response.results && response.results.length > 0) {
1043
- const photoUrl = response.results[0].urls.full;
1050
+ let photoUrl = response.results[0].urls.regular;
1051
+ if (photoUrl.includes("?")) {
1052
+ photoUrl += "&fm=webp&q=85";
1053
+ } else {
1054
+ photoUrl += "?fm=webp&q=85";
1055
+ }
1044
1056
  if (debug) ctx.logger("pig").debug(`Unsplash: Found photo URL: ${photoUrl}`);
1045
1057
  return photoUrl;
1046
1058
  }
@@ -1287,29 +1299,38 @@ __name(triggerTravelSequence, "triggerTravelSequence");
1287
1299
 
1288
1300
  // src/config.ts
1289
1301
  var import_koishi = require("koishi");
1290
- var Config = import_koishi.Schema.object({
1291
- sunriseApi: import_koishi.Schema.string().default("https://api.sunrise-sunset.org/json").description("日出日落 API 地址"),
1292
- defaultLat: import_koishi.Schema.number().default(30).description("默认纬度(北纬为正,南纬为负,范围 -90 到 90)"),
1293
- defaultLng: import_koishi.Schema.number().default(120).description("默认经度(东经为正,西经为负,范围 -180 180)"),
1294
- abnormalThreshold: import_koishi.Schema.number().default(3).description("异常作息阈值(小时)"),
1295
- outputMode: import_koishi.Schema.union(["text", "image"]).default("text").description("输出模式"),
1296
- useStorageService: import_koishi.Schema.boolean().default(true).description("使用 chatluna-storage-service 管理文件缓存(推荐,需安装该插件)。关闭时使用 base64 直接发送。"),
1297
- storageCacheHours: import_koishi.Schema.number().default(24).description("存储服务缓存时间(小时)"),
1298
- travelMessageTemplate: import_koishi.Schema.string().default("去了 {landmark},{country}!📸").description("旅行消息模板(可用变量:{landmark} 地标名, {country} 国家名)"),
1299
- aigcEnabled: import_koishi.Schema.boolean().default(false).description("启用 AI 生成图片(需要 media-luna 插件)"),
1300
- aigcChannel: import_koishi.Schema.string().default("").description("AI 生图渠道名称(media-luna 渠道)"),
1301
- aigcPrompt: import_koishi.Schema.string().default("一个可爱的卡通小猪正在 {country} 的 {landmark} 前面自拍,阳光明媚,旅游照片风格").description("AI 生图提示词模板"),
1302
- logPath: import_koishi.Schema.string().default("./data/pig/logs").description("旅行日志存储路径(仅在不使用存储服务时生效)"),
1303
- // LLM location generation
1304
- llmLocationEnabled: import_koishi.Schema.boolean().default(false).description("启用 LLM 动态生成旅行地点(需要 chatluna 插件)"),
1305
- llmLocationModel: import_koishi.Schema.dynamic("model").description("用于生成地点的 LLM 模型(需要支持中文输出)"),
1306
- // Unsplash API
1307
- unsplashAccessKey: import_koishi.Schema.string().default("").description("Unsplash API Access Key(从 unsplash.com/developers 获取,用于获取高质量背景图)"),
1308
- // Travel log retention
1309
- logRetentionDays: import_koishi.Schema.number().default(45).description("旅行日志保留天数(默认45天,约1.5个月)"),
1310
- // Debug
1311
- debug: import_koishi.Schema.boolean().default(false).description("启用调试模式(输出详细日志)")
1312
- });
1302
+ var Config = import_koishi.Schema.intersect([
1303
+ import_koishi.Schema.object({
1304
+ outputMode: import_koishi.Schema.union(["text", "image"]).default("image").description("输出模式:text 纯文本,image 生成精美卡片"),
1305
+ travelMessageTemplate: import_koishi.Schema.string().default("去了 {landmark},{country}!📸").description("旅行消息模板(可用变量:{landmark} 地标名, {country} 国家名)")
1306
+ }).description("基础设置"),
1307
+ import_koishi.Schema.object({
1308
+ llmLocationEnabled: import_koishi.Schema.boolean().default(false).description("启用后使用 LLM 动态生成全球旅行地点,关闭则使用预设地点库"),
1309
+ llmLocationModel: import_koishi.Schema.dynamic("model").description("用于生成地点的模型(推荐使用快速模型如 gemini-flash)"),
1310
+ unsplashAccessKey: import_koishi.Schema.string().role("secret").default("").description("用于获取高质量风景背景图(从 unsplash.com/developers 免费申请)")
1311
+ }).description("地点与图片 🌍"),
1312
+ import_koishi.Schema.object({
1313
+ aigcEnabled: import_koishi.Schema.boolean().default(false).description("启用后使用 AI 生成小猪旅行插画(需要 media-luna 插件)"),
1314
+ aigcChannel: import_koishi.Schema.string().default("").description("media-luna 渠道名称"),
1315
+ aigcPrompt: import_koishi.Schema.string().role("textarea").default("一个可爱的卡通小猪正在 {country} 的 {landmark} 前面自拍,阳光明媚,旅游照片风格").description("AI 生图提示词模板")
1316
+ }).description("AI 生图(可选)🎨"),
1317
+ import_koishi.Schema.object({
1318
+ experimentalAutoDetect: import_koishi.Schema.boolean().default(false).description("自动检测用户首条消息并判断作息是否异常"),
1319
+ sunriseApi: import_koishi.Schema.string().default("https://api.sunrise-sunset.org/json").description("日出日落 API 地址"),
1320
+ defaultLat: import_koishi.Schema.number().default(30).description("默认纬度(北纬为正)"),
1321
+ defaultLng: import_koishi.Schema.number().default(120).description("默认经度(东经为正)"),
1322
+ abnormalThreshold: import_koishi.Schema.number().default(3).description("作息异常判定阈值(小时)")
1323
+ }).description("自动检测(实验性)🧪"),
1324
+ import_koishi.Schema.object({
1325
+ useStorageService: import_koishi.Schema.boolean().default(true).description("使用 chatluna-storage-service 缓存图片(推荐)"),
1326
+ storageCacheHours: import_koishi.Schema.number().default(24).description("图片缓存时间(小时)"),
1327
+ logRetentionDays: import_koishi.Schema.number().default(45).description("旅行记录保留天数"),
1328
+ logPath: import_koishi.Schema.string().default("./data/pig/logs").description("本地日志存储路径(仅在不使用存储服务时生效)")
1329
+ }).description("存储设置 💾"),
1330
+ import_koishi.Schema.object({
1331
+ debug: import_koishi.Schema.boolean().default(false).description("输出详细调试日志")
1332
+ }).description("调试")
1333
+ ]);
1313
1334
 
1314
1335
  // src/index.ts
1315
1336
  var name = "my-pig-group-friends";
@@ -1339,9 +1360,17 @@ function apply(ctx, config) {
1339
1360
  if (config.useStorageService && !ctx.chatluna_storage) {
1340
1361
  ctx.logger("pig").warn("useStorageService 已启用但 chatluna_storage 服务不可用,将回退到 base64 模式");
1341
1362
  }
1342
- ctx.command("pig <user:user>", "虚拟旅行").action(async ({ session }, user) => {
1343
- if (!user) return "请指定一个用户";
1344
- const [platform, userId] = user.split(":");
1363
+ const travelLocks = /* @__PURE__ */ new Map();
1364
+ const LOCK_DURATION_MS = 60 * 1e3;
1365
+ ctx.command("pig [user:user]", "虚拟旅行").alias("猪醒").action(async ({ session }, user) => {
1366
+ let platform;
1367
+ let userId;
1368
+ if (user) {
1369
+ [platform, userId] = user.split(":");
1370
+ } else {
1371
+ platform = session.platform;
1372
+ userId = session.userId;
1373
+ }
1345
1374
  let userInfo;
1346
1375
  if (session?.userId === userId) {
1347
1376
  userInfo = {
@@ -1350,18 +1379,55 @@ function apply(ctx, config) {
1350
1379
  avatarUrl: session.author?.avatar || ""
1351
1380
  };
1352
1381
  } else {
1382
+ let avatarUrl = "";
1383
+ let username = userId;
1384
+ try {
1385
+ if (platform === "onebot") {
1386
+ avatarUrl = `https://q.qlogo.cn/headimg_dl?dst_uin=${userId}&spec=640`;
1387
+ }
1388
+ if (session.bot) {
1389
+ if (session.guildId && session.bot.getGuildMember) {
1390
+ try {
1391
+ const member = await session.bot.getGuildMember(session.guildId, userId);
1392
+ username = member.nick || member.name || member.user?.name || username;
1393
+ avatarUrl = avatarUrl || member.user?.avatar || "";
1394
+ } catch (e) {
1395
+ }
1396
+ }
1397
+ if (username === userId && session.bot.getUser) {
1398
+ try {
1399
+ const user2 = await session.bot.getUser(userId);
1400
+ username = user2.name || username;
1401
+ avatarUrl = avatarUrl || user2.avatar || "";
1402
+ } catch (e) {
1403
+ }
1404
+ }
1405
+ }
1406
+ } catch (e) {
1407
+ ctx.logger("pig").warn(`Failed to fetch metadata for user ${userId}: ${e}`);
1408
+ }
1353
1409
  userInfo = {
1354
1410
  userId,
1355
- username: userId,
1356
- avatarUrl: ""
1411
+ username,
1412
+ avatarUrl
1357
1413
  };
1358
1414
  }
1359
1415
  const result = await triggerTravelSequence(ctx, config, userInfo, platform);
1360
1416
  return formatTravelMessage(result, userId, config);
1361
1417
  });
1362
1418
  ctx.middleware(async (session, next) => {
1419
+ if (!config.experimentalAutoDetect) return next();
1363
1420
  if (!session.userId || !session.content) return next();
1364
- const now = /* @__PURE__ */ new Date();
1421
+ const lockKey = `${session.platform}:${session.userId}`;
1422
+ const now = Date.now();
1423
+ const lockTime = travelLocks.get(lockKey);
1424
+ if (lockTime && now - lockTime < LOCK_DURATION_MS) {
1425
+ if (config.debug) {
1426
+ ctx.logger("pig").debug(`用户 ${session.userId} 处于锁定期,跳过自动检测`);
1427
+ }
1428
+ return next();
1429
+ }
1430
+ const nowDate = /* @__PURE__ */ new Date();
1365
1431
  const [userState] = await ctx.database.get("pig_user_state", {
1366
1432
  userId: session.userId,
1367
1433
  platform: session.platform
@@ -1371,10 +1437,17 @@ function apply(ctx, config) {
1371
1437
  try {
1372
1438
  const sunriseInfo = await getSunriseInfo(ctx, lat, lng);
1373
1439
  const dayStart = new Date(sunriseInfo.sunrise.getTime() - 2 * 60 * 60 * 1e3);
1374
- if (now >= dayStart && (!userState?.lastWakeUp || userState.lastWakeUp < dayStart)) {
1440
+ if (nowDate >= dayStart && (!userState?.lastWakeUp || userState.lastWakeUp < dayStart)) {
1441
+ await ctx.database.upsert("pig_user_state", [{
1442
+ userId: session.userId,
1443
+ platform: session.platform,
1444
+ lastWakeUp: nowDate,
1445
+ lastSunrise: sunriseInfo.sunrise
1446
+ }], ["platform", "userId"]);
1375
1447
  if (userState?.lastWakeUp) {
1376
- const diffHours = Math.abs((now.getTime() - userState.lastWakeUp.getTime() - 24 * 60 * 60 * 1e3) / (1e3 * 60 * 60));
1448
+ const diffHours = Math.abs((nowDate.getTime() - userState.lastWakeUp.getTime() - 24 * 60 * 60 * 1e3) / (1e3 * 60 * 60));
1377
1449
  if (diffHours > config.abnormalThreshold) {
1450
+ travelLocks.set(lockKey, now);
1378
1451
  await session.send(`检测到 ${import_koishi2.segment.at(session.userId)} 作息异常(差异: ${diffHours.toFixed(1)}小时),准备虚拟旅行...`);
1379
1452
  const userInfo = {
1380
1453
  userId: session.userId,
@@ -1385,12 +1458,6 @@ function apply(ctx, config) {
1385
1458
  await session.send(formatTravelMessage(result, session.userId, config));
1386
1459
  }
1387
1460
  }
1388
- await ctx.database.upsert("pig_user_state", [{
1389
- userId: session.userId,
1390
- platform: session.platform,
1391
- lastWakeUp: now,
1392
- lastSunrise: sunriseInfo.sunrise
1393
- }]);
1394
1461
  }
1395
1462
  } catch (e) {
1396
1463
  ctx.logger("pig").error("Failed to process wake-up detection:", e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-my-pig-group-friends",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "files": [
@@ -21,13 +21,18 @@
21
21
  },
22
22
  "koishi": {
23
23
  "description": {
24
- "zh": "我的猪群群友插件"
24
+ "zh": "猪醒 - 虚拟旅行打卡插件,让你的猪猪群友环游世界"
25
25
  },
26
26
  "service": {
27
27
  "required": [
28
28
  "database",
29
- "cron"
29
+ "cron",
30
+ "puppeteer"
31
+ ],
32
+ "optional": [
33
+ "chatluna",
34
+ "chatluna_storage"
30
35
  ]
31
36
  }
32
37
  }
33
- }
38
+ }