koishi-plugin-my-pig-group-friends 1.0.0 → 1.1.1

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,8 @@ export interface Config {
16
16
  llmLocationModel: string;
17
17
  unsplashAccessKey: string;
18
18
  logRetentionDays: number;
19
+ experimentalAutoDetect: boolean;
19
20
  debug: boolean;
21
+ emojiFont: 'Noto Color Emoji' | 'Twemoji' | 'System';
20
22
  }
21
23
  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"}`);
@@ -592,14 +177,14 @@ async function generateFootprintCard(ctx, config, data, userInfo, platform, back
592
177
  }
593
178
  const now = /* @__PURE__ */ new Date();
594
179
  const dateStr = `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日`;
180
+ const emojiFont = config.emojiFont === "System" ? "" : `"${config.emojiFont}", `;
595
181
  const html = `
596
182
  <!DOCTYPE html>
597
183
  <html>
598
184
  <head>
599
185
  <meta charset="UTF-8">
600
186
  <style>
601
- @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700;900&display=swap');
602
-
187
+ /* System fonts only - no external font loading for Docker compatibility */
603
188
  * {
604
189
  margin: 0;
605
190
  padding: 0;
@@ -610,7 +195,8 @@ async function generateFootprintCard(ctx, config, data, userInfo, platform, back
610
195
  width: 1080px;
611
196
  height: 1920px;
612
197
  overflow: hidden;
613
- font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif;
198
+ /* Prioritize system fonts with wide Unicode coverage for emoji and CJK support */
199
+ font-family: ${emojiFont}"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
200
  background: #f0f0f2;
615
201
  --bg-image: url('${bgImage}');
616
202
  }
@@ -689,11 +275,11 @@ async function generateFootprintCard(ctx, config, data, userInfo, platform, back
689
275
  content: "";
690
276
  position: absolute;
691
277
  inset: 0;
692
- /* 关键:使用 fixed attachment 模拟透视背景 */
278
+ /* Use scroll instead of fixed to avoid Puppeteer rendering issues */
693
279
  background-image: var(--bg-image);
694
- background-attachment: fixed;
280
+ background-attachment: scroll;
695
281
  background-size: cover;
696
- background-position: center;
282
+ background-position: center bottom;
697
283
 
698
284
  /* 模糊处理,模拟毛玻璃内部的散射 */
699
285
  filter: blur(25px) brightness(1.1);
@@ -935,11 +521,17 @@ async function generateFootprintCard(ctx, config, data, userInfo, platform, back
935
521
  for (let i = 0; i < allElements.length; i++) {
936
522
  const bg = window.getComputedStyle(allElements[i]).backgroundImage;
937
523
  if (bg && bg !== 'none' && bg.includes('url(')) {
938
- const urlMatch = bg.match(/url(["']?(.+?)["']?)/);
939
- if (urlMatch) {
940
- console.log('Found background image: ' + urlMatch[1]);
524
+ const urlMatch = bg.match(/url(s*?["']?(.+?)["']?s*?)/);
525
+ if (urlMatch && urlMatch[1]) {
526
+ const url = urlMatch[1];
527
+ // Skip data URLs as they don't need pre-loading and can be fragile in Image constructor
528
+ if (url.startsWith('data:')) {
529
+ console.log('Skipping data-url pre-load');
530
+ continue;
531
+ }
532
+ console.log('Found background image: ' + url);
941
533
  const img = new Image();
942
- img.src = urlMatch[1];
534
+ img.src = url;
943
535
  bgImages.push(img);
944
536
  }
945
537
  }
@@ -970,16 +562,17 @@ async function generateFootprintCard(ctx, config, data, userInfo, platform, back
970
562
  ]);
971
563
 
972
564
  console.log('All images and fonts loaded');
973
- // Extra safety delay for rendering
974
- await new Promise(r => setTimeout(r, 400));
565
+ // Extra safety delay for rendering (reduced from 400ms)
566
+ await new Promise(r => setTimeout(r, 200));
975
567
  }
976
568
  window.renderReady = waitForImages();
977
569
  </script>
978
570
  </body>
979
571
  </html>
980
572
  `;
573
+ let page = null;
981
574
  try {
982
- const page = await ctx.puppeteer.page();
575
+ page = await ctx.puppeteer.page();
983
576
  if (config.debug) {
984
577
  page.on("console", (msg) => ctx.logger("pig").debug(`[Browser] ${msg.text()}`));
985
578
  page.on("requestfailed", (request) => {
@@ -987,30 +580,445 @@ async function generateFootprintCard(ctx, config, data, userInfo, platform, back
987
580
  });
988
581
  }
989
582
  await page.setViewport({ width: 1080, height: 1920, deviceScaleFactor: 1 });
990
- await page.setContent(html, { waitUntil: "networkidle0" });
583
+ await page.setContent(html, { waitUntil: "domcontentloaded" });
991
584
  await page.evaluate(() => window["renderReady"]);
992
585
  const buffer = await page.screenshot({ type: "png", fullPage: true });
993
- await page.close();
994
586
  const filename = `pig_${userInfo.userId}_${now.getTime()}.png`;
995
587
  ctx.logger("pig").info(`足迹卡片已生成: ${filename}`);
996
588
  return { buffer, filename };
997
589
  } catch (e) {
998
590
  ctx.logger("pig").error("Failed to generate card", e);
999
591
  throw e;
592
+ } finally {
593
+ if (page) {
594
+ try {
595
+ await page.close();
596
+ } catch (closeError) {
597
+ if (config.debug) {
598
+ ctx.logger("pig").warn(`Failed to close puppeteer page: ${closeError}`);
599
+ }
600
+ }
601
+ }
602
+ }
603
+ }
604
+ __name(generateFootprintCard, "generateFootprintCard");
605
+ function escapeHtml(text) {
606
+ const map = {
607
+ "&": "&amp;",
608
+ "<": "&lt;",
609
+ ">": "&gt;",
610
+ '"': "&quot;",
611
+ "'": "&#039;"
612
+ };
613
+ return text.replace(/[&<>"']/g, (m) => map[m]);
614
+ }
615
+ __name(escapeHtml, "escapeHtml");
616
+
617
+ // src/constants.ts
618
+ var LOCATIONS = [
619
+ // 亚洲 (UTC+5 ~ UTC+9)
620
+ {
621
+ country: "Japan",
622
+ countryZh: "日本",
623
+ city: "Kyoto",
624
+ landmark: "Fushimi Inari Shrine",
625
+ landmarkZh: "伏见稻荷大社",
626
+ timezone: "Asia/Tokyo",
627
+ landscapeUrl: "https://images.unsplash.com/photo-1478436127897-769e1b3f0f36"
628
+ },
629
+ {
630
+ country: "Japan",
631
+ countryZh: "日本",
632
+ city: "Osaka",
633
+ landmark: "Dotonbori",
634
+ landmarkZh: "道顿堀",
635
+ timezone: "Asia/Tokyo",
636
+ landscapeUrl: "https://images.unsplash.com/photo-1590559899731-a382839e5549"
637
+ },
638
+ {
639
+ country: "South Korea",
640
+ countryZh: "韩国",
641
+ city: "Seoul",
642
+ landmark: "Gyeongbokgung Palace",
643
+ landmarkZh: "景福宫",
644
+ timezone: "Asia/Seoul",
645
+ landscapeUrl: "https://images.unsplash.com/photo-1534274988757-a28bf1a57c17"
646
+ },
647
+ {
648
+ country: "China",
649
+ countryZh: "中国",
650
+ city: "Shanghai",
651
+ landmark: "The Bund",
652
+ landmarkZh: "外滩",
653
+ timezone: "Asia/Shanghai",
654
+ landscapeUrl: "https://images.unsplash.com/photo-1537531383496-f4749b918caa"
655
+ },
656
+ {
657
+ country: "China",
658
+ countryZh: "中国",
659
+ city: "Guilin",
660
+ landmark: "Li River",
661
+ landmarkZh: "漓江",
662
+ timezone: "Asia/Shanghai",
663
+ landscapeUrl: "https://images.unsplash.com/photo-1529921879218-f99546d03a50"
664
+ },
665
+ {
666
+ country: "Thailand",
667
+ countryZh: "泰国",
668
+ city: "Bangkok",
669
+ landmark: "Grand Palace",
670
+ landmarkZh: "大皇宫",
671
+ timezone: "Asia/Bangkok",
672
+ landscapeUrl: "https://images.unsplash.com/photo-1563492065599-3520f775eeed"
673
+ },
674
+ {
675
+ country: "Vietnam",
676
+ countryZh: "越南",
677
+ city: "Ha Long Bay",
678
+ landmark: "Ha Long Bay",
679
+ landmarkZh: "下龙湾",
680
+ timezone: "Asia/Ho_Chi_Minh",
681
+ landscapeUrl: "https://images.unsplash.com/photo-1528127269322-539801943592"
682
+ },
683
+ {
684
+ country: "India",
685
+ countryZh: "印度",
686
+ city: "Agra",
687
+ landmark: "Taj Mahal",
688
+ landmarkZh: "泰姬陵",
689
+ timezone: "Asia/Kolkata",
690
+ landscapeUrl: "https://images.unsplash.com/photo-1564507592333-c60657eea523"
691
+ },
692
+ {
693
+ country: "Singapore",
694
+ countryZh: "新加坡",
695
+ city: "Singapore",
696
+ landmark: "Marina Bay Sands",
697
+ landmarkZh: "滨海湾金沙",
698
+ timezone: "Asia/Singapore",
699
+ landscapeUrl: "https://images.unsplash.com/photo-1525625293386-3f8f99389edd"
700
+ },
701
+ {
702
+ country: "Indonesia",
703
+ countryZh: "印度尼西亚",
704
+ city: "Bali",
705
+ landmark: "Tanah Lot Temple",
706
+ landmarkZh: "海神庙",
707
+ timezone: "Asia/Jakarta",
708
+ landscapeUrl: "https://images.unsplash.com/photo-1537996194471-e657df975ab4"
709
+ },
710
+ {
711
+ country: "Nepal",
712
+ countryZh: "尼泊尔",
713
+ city: "Kathmandu",
714
+ landmark: "Himalaya Mountains",
715
+ landmarkZh: "喜马拉雅山",
716
+ timezone: "Asia/Kathmandu",
717
+ landscapeUrl: "https://images.unsplash.com/photo-1544735716-392fe2489ffa"
718
+ },
719
+ {
720
+ country: "UAE",
721
+ countryZh: "阿联酋",
722
+ city: "Dubai",
723
+ landmark: "Burj Khalifa",
724
+ landmarkZh: "哈利法塔",
725
+ timezone: "Asia/Dubai",
726
+ landscapeUrl: "https://images.unsplash.com/photo-1512453979798-5ea266f8880c"
727
+ },
728
+ // 欧洲 (UTC+0 ~ UTC+3)
729
+ {
730
+ country: "UK",
731
+ countryZh: "英国",
732
+ city: "London",
733
+ landmark: "Big Ben",
734
+ landmarkZh: "大本钟",
735
+ timezone: "Europe/London",
736
+ landscapeUrl: "https://images.unsplash.com/photo-1513635269975-59663e0ac1ad"
737
+ },
738
+ {
739
+ country: "France",
740
+ countryZh: "法国",
741
+ city: "Provence",
742
+ landmark: "Lavender Fields",
743
+ landmarkZh: "薰衣草田",
744
+ timezone: "Europe/Paris",
745
+ landscapeUrl: "https://images.unsplash.com/photo-1499002238440-d264edd596ec"
746
+ },
747
+ {
748
+ country: "Italy",
749
+ countryZh: "意大利",
750
+ city: "Venice",
751
+ landmark: "Grand Canal",
752
+ landmarkZh: "大运河",
753
+ timezone: "Europe/Rome",
754
+ landscapeUrl: "https://images.unsplash.com/photo-1514890547357-a9ee288728e0"
755
+ },
756
+ {
757
+ country: "Italy",
758
+ countryZh: "意大利",
759
+ city: "Rome",
760
+ landmark: "Colosseum",
761
+ landmarkZh: "罗马斗兽场",
762
+ timezone: "Europe/Rome",
763
+ landscapeUrl: "https://images.unsplash.com/photo-1552832230-c0197dd311b5"
764
+ },
765
+ {
766
+ country: "Spain",
767
+ countryZh: "西班牙",
768
+ city: "Barcelona",
769
+ landmark: "Sagrada Familia",
770
+ landmarkZh: "圣家堂",
771
+ timezone: "Europe/Madrid",
772
+ landscapeUrl: "https://images.unsplash.com/photo-1583422409516-2895a77efded"
773
+ },
774
+ {
775
+ country: "Greece",
776
+ countryZh: "希腊",
777
+ city: "Santorini",
778
+ landmark: "Oia Village",
779
+ landmarkZh: "伊亚小镇",
780
+ timezone: "Europe/Athens",
781
+ landscapeUrl: "https://images.unsplash.com/photo-1613395877344-13d4a8e0d49e"
782
+ },
783
+ {
784
+ country: "Netherlands",
785
+ countryZh: "荷兰",
786
+ city: "Amsterdam",
787
+ landmark: "Canal Ring",
788
+ landmarkZh: "运河环",
789
+ timezone: "Europe/Amsterdam",
790
+ landscapeUrl: "https://images.unsplash.com/photo-1534351590666-13e3e96b5017"
791
+ },
792
+ {
793
+ country: "Switzerland",
794
+ countryZh: "瑞士",
795
+ city: "Interlaken",
796
+ landmark: "Swiss Alps",
797
+ landmarkZh: "阿尔卑斯山",
798
+ timezone: "Europe/Zurich",
799
+ landscapeUrl: "https://images.unsplash.com/photo-1531366936337-7c912a4589a7"
800
+ },
801
+ {
802
+ country: "Norway",
803
+ countryZh: "挪威",
804
+ city: "Tromsø",
805
+ landmark: "Northern Lights",
806
+ landmarkZh: "北极光",
807
+ timezone: "Europe/Oslo",
808
+ landscapeUrl: "https://images.unsplash.com/photo-1483347756197-71ef80e95f73"
809
+ },
810
+ {
811
+ country: "Iceland",
812
+ countryZh: "冰岛",
813
+ city: "Reykjavik",
814
+ landmark: "Blue Lagoon",
815
+ landmarkZh: "蓝湖温泉",
816
+ timezone: "Atlantic/Reykjavik",
817
+ landscapeUrl: "https://images.unsplash.com/photo-1504829857797-ddff29c27927"
818
+ },
819
+ {
820
+ country: "Turkey",
821
+ countryZh: "土耳其",
822
+ city: "Cappadocia",
823
+ landmark: "Hot Air Balloons",
824
+ landmarkZh: "热气球",
825
+ timezone: "Europe/Istanbul",
826
+ landscapeUrl: "https://images.unsplash.com/photo-1641128324972-af3212f0f6bd"
827
+ },
828
+ {
829
+ country: "Russia",
830
+ countryZh: "俄罗斯",
831
+ city: "Moscow",
832
+ landmark: "Red Square",
833
+ landmarkZh: "红场",
834
+ timezone: "Europe/Moscow",
835
+ landscapeUrl: "https://images.unsplash.com/photo-1513326738677-b964603b136d"
836
+ },
837
+ // 非洲 (UTC+0 ~ UTC+3)
838
+ {
839
+ country: "Egypt",
840
+ countryZh: "埃及",
841
+ city: "Giza",
842
+ landmark: "Pyramids of Giza",
843
+ landmarkZh: "吉萨金字塔",
844
+ timezone: "Africa/Cairo",
845
+ landscapeUrl: "https://images.unsplash.com/photo-1503177119275-0aa32b3a9368"
846
+ },
847
+ {
848
+ country: "Morocco",
849
+ countryZh: "摩洛哥",
850
+ city: "Marrakech",
851
+ landmark: "Jardin Majorelle",
852
+ landmarkZh: "马约尔花园",
853
+ timezone: "Africa/Casablanca",
854
+ landscapeUrl: "https://images.unsplash.com/photo-1489749798305-4fea3ae63d43"
855
+ },
856
+ {
857
+ country: "South Africa",
858
+ countryZh: "南非",
859
+ city: "Cape Town",
860
+ landmark: "Table Mountain",
861
+ landmarkZh: "桌山",
862
+ timezone: "Africa/Johannesburg",
863
+ landscapeUrl: "https://images.unsplash.com/photo-1580060839134-75a5edca2e99"
864
+ },
865
+ {
866
+ country: "Kenya",
867
+ countryZh: "肯尼亚",
868
+ city: "Masai Mara",
869
+ landmark: "Safari Savanna",
870
+ landmarkZh: "马赛马拉草原",
871
+ timezone: "Africa/Nairobi",
872
+ landscapeUrl: "https://images.unsplash.com/photo-1547970810-dc1eac37d174"
873
+ },
874
+ // 北美洲 (UTC-5 ~ UTC-8)
875
+ {
876
+ country: "USA",
877
+ countryZh: "美国",
878
+ city: "New York",
879
+ landmark: "Times Square",
880
+ landmarkZh: "时代广场",
881
+ timezone: "America/New_York",
882
+ landscapeUrl: "https://images.unsplash.com/photo-1534430480872-3498386e7856"
883
+ },
884
+ {
885
+ country: "USA",
886
+ countryZh: "美国",
887
+ city: "San Francisco",
888
+ landmark: "Golden Gate Bridge",
889
+ landmarkZh: "金门大桥",
890
+ timezone: "America/Los_Angeles",
891
+ landscapeUrl: "https://images.unsplash.com/photo-1449034446853-66c86144b0ad"
892
+ },
893
+ {
894
+ country: "USA",
895
+ countryZh: "美国",
896
+ city: "Las Vegas",
897
+ landmark: "The Strip",
898
+ landmarkZh: "拉斯维加斯大道",
899
+ timezone: "America/Los_Angeles",
900
+ landscapeUrl: "https://images.unsplash.com/photo-1605833556294-ea5c7a74f57d"
901
+ },
902
+ {
903
+ country: "USA",
904
+ countryZh: "美国",
905
+ city: "Arizona",
906
+ landmark: "Grand Canyon",
907
+ landmarkZh: "大峡谷",
908
+ timezone: "America/Phoenix",
909
+ landscapeUrl: "https://images.unsplash.com/photo-1474044159687-1ee9f3a51722"
910
+ },
911
+ {
912
+ country: "Canada",
913
+ countryZh: "加拿大",
914
+ city: "Banff",
915
+ landmark: "Lake Louise",
916
+ landmarkZh: "路易斯湖",
917
+ timezone: "America/Edmonton",
918
+ landscapeUrl: "https://images.unsplash.com/photo-1502085671122-2d218cd434e6"
919
+ },
920
+ {
921
+ country: "Canada",
922
+ countryZh: "加拿大",
923
+ city: "Niagara Falls",
924
+ landmark: "Niagara Falls",
925
+ landmarkZh: "尼亚加拉瀑布",
926
+ timezone: "America/Toronto",
927
+ landscapeUrl: "https://images.unsplash.com/photo-1489447068241-b3490214e879"
928
+ },
929
+ {
930
+ country: "Mexico",
931
+ countryZh: "墨西哥",
932
+ city: "Cancun",
933
+ landmark: "Chichen Itza",
934
+ landmarkZh: "奇琴伊察",
935
+ timezone: "America/Cancun",
936
+ landscapeUrl: "https://images.unsplash.com/photo-1518638150340-f706e86654de"
937
+ },
938
+ // 南美洲 (UTC-3 ~ UTC-5)
939
+ {
940
+ country: "Brazil",
941
+ countryZh: "巴西",
942
+ city: "Rio de Janeiro",
943
+ landmark: "Christ the Redeemer",
944
+ landmarkZh: "基督救世主像",
945
+ timezone: "America/Sao_Paulo",
946
+ landscapeUrl: "https://images.unsplash.com/photo-1483729558449-99ef09a8c325"
947
+ },
948
+ {
949
+ country: "Peru",
950
+ countryZh: "秘鲁",
951
+ city: "Cusco",
952
+ landmark: "Machu Picchu",
953
+ landmarkZh: "马丘比丘",
954
+ timezone: "America/Lima",
955
+ landscapeUrl: "https://images.unsplash.com/photo-1526392060635-9d6019884377"
956
+ },
957
+ {
958
+ country: "Argentina",
959
+ countryZh: "阿根廷",
960
+ city: "Patagonia",
961
+ landmark: "Perito Moreno Glacier",
962
+ landmarkZh: "莫雷诺冰川",
963
+ timezone: "America/Argentina/Buenos_Aires",
964
+ landscapeUrl: "https://images.unsplash.com/photo-1551279880-03041531948f"
965
+ },
966
+ {
967
+ country: "Chile",
968
+ countryZh: "智利",
969
+ city: "Atacama",
970
+ landmark: "Atacama Desert",
971
+ landmarkZh: "阿塔卡马沙漠",
972
+ timezone: "America/Santiago",
973
+ landscapeUrl: "https://images.unsplash.com/photo-1489392191049-fc10c97e64b6"
974
+ },
975
+ // 大洋洲 (UTC+10 ~ UTC+12)
976
+ {
977
+ country: "Australia",
978
+ countryZh: "澳大利亚",
979
+ city: "Sydney",
980
+ landmark: "Sydney Opera House",
981
+ landmarkZh: "悉尼歌剧院",
982
+ timezone: "Australia/Sydney",
983
+ landscapeUrl: "https://images.unsplash.com/photo-1506973035872-a4ec16b8e8d9"
984
+ },
985
+ {
986
+ country: "Australia",
987
+ countryZh: "澳大利亚",
988
+ city: "Queensland",
989
+ landmark: "Great Barrier Reef",
990
+ landmarkZh: "大堡礁",
991
+ timezone: "Australia/Brisbane",
992
+ landscapeUrl: "https://images.unsplash.com/photo-1559128010-7c1ad6e1b6a5"
993
+ },
994
+ {
995
+ country: "New Zealand",
996
+ countryZh: "新西兰",
997
+ city: "Queenstown",
998
+ landmark: "Milford Sound",
999
+ landmarkZh: "米尔福德峡湾",
1000
+ timezone: "Pacific/Auckland",
1001
+ landscapeUrl: "https://images.unsplash.com/photo-1507699622108-4be3abd695ad"
1002
+ },
1003
+ {
1004
+ country: "New Zealand",
1005
+ countryZh: "新西兰",
1006
+ city: "Matamata",
1007
+ landmark: "Hobbiton",
1008
+ landmarkZh: "霍比屯",
1009
+ timezone: "Pacific/Auckland",
1010
+ landscapeUrl: "https://images.unsplash.com/photo-1507097634215-e82e6b552cae"
1011
+ },
1012
+ {
1013
+ country: "Fiji",
1014
+ countryZh: "斐济",
1015
+ city: "Nadi",
1016
+ landmark: "Fiji Islands",
1017
+ landmarkZh: "斐济群岛",
1018
+ timezone: "Pacific/Fiji",
1019
+ landscapeUrl: "https://images.unsplash.com/photo-1589179899031-d86b7e3fcd61"
1000
1020
  }
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");
1021
+ ];
1014
1022
 
1015
1023
  // src/services/location.ts
1016
1024
  var import_messages = require("@langchain/core/messages");
@@ -1040,7 +1048,12 @@ async function searchUnsplashPhoto(ctx, accessKey, query, debug = false) {
1040
1048
  }
1041
1049
  );
1042
1050
  if (response.results && response.results.length > 0) {
1043
- const photoUrl = response.results[0].urls.full;
1051
+ let photoUrl = response.results[0].urls.regular;
1052
+ if (photoUrl.includes("?")) {
1053
+ photoUrl += "&fm=webp&q=85";
1054
+ } else {
1055
+ photoUrl += "?fm=webp&q=85";
1056
+ }
1044
1057
  if (debug) ctx.logger("pig").debug(`Unsplash: Found photo URL: ${photoUrl}`);
1045
1058
  return photoUrl;
1046
1059
  }
@@ -1287,29 +1300,43 @@ __name(triggerTravelSequence, "triggerTravelSequence");
1287
1300
 
1288
1301
  // src/config.ts
1289
1302
  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
- });
1303
+ var Config = import_koishi.Schema.intersect([
1304
+ import_koishi.Schema.object({
1305
+ outputMode: import_koishi.Schema.union(["text", "image"]).default("image").description("输出模式:text 纯文本,image 生成精美卡片"),
1306
+ emojiFont: import_koishi.Schema.union([
1307
+ import_koishi.Schema.const("System").description("系统默认"),
1308
+ import_koishi.Schema.const("Noto Color Emoji").description("Noto Color Emoji"),
1309
+ import_koishi.Schema.const("Twemoji").description("Twemoji (Twitter Emoji)")
1310
+ ]).default("System").description("Emoji 字体偏好(需确保容器内已安装相应字体)"),
1311
+ travelMessageTemplate: import_koishi.Schema.string().default("去了 {landmark},{country}!📸").description("旅行消息模板(可用变量:{landmark} 地标名, {country} 国家名)")
1312
+ }).description("基础设置"),
1313
+ import_koishi.Schema.object({
1314
+ llmLocationEnabled: import_koishi.Schema.boolean().default(false).description("启用后使用 LLM 动态生成全球旅行地点,关闭则使用预设地点库"),
1315
+ llmLocationModel: import_koishi.Schema.dynamic("model").description("用于生成地点的模型(推荐使用快速模型如 gemini-flash)"),
1316
+ unsplashAccessKey: import_koishi.Schema.string().role("secret").default("").description("用于获取高质量风景背景图(从 unsplash.com/developers 免费申请)")
1317
+ }).description("地点与图片 🌍"),
1318
+ import_koishi.Schema.object({
1319
+ aigcEnabled: import_koishi.Schema.boolean().default(false).description("启用后使用 AI 生成小猪旅行插画(需要 media-luna 插件)"),
1320
+ aigcChannel: import_koishi.Schema.string().default("").description("media-luna 渠道名称"),
1321
+ aigcPrompt: import_koishi.Schema.string().role("textarea").default("一个可爱的卡通小猪正在 {country} 的 {landmark} 前面自拍,阳光明媚,旅游照片风格").description("AI 生图提示词模板")
1322
+ }).description("AI 生图(可选)🎨"),
1323
+ import_koishi.Schema.object({
1324
+ experimentalAutoDetect: import_koishi.Schema.boolean().default(false).description("自动检测用户首条消息并判断作息是否异常"),
1325
+ sunriseApi: import_koishi.Schema.string().default("https://api.sunrise-sunset.org/json").description("日出日落 API 地址"),
1326
+ defaultLat: import_koishi.Schema.number().default(30).description("默认纬度(北纬为正)"),
1327
+ defaultLng: import_koishi.Schema.number().default(120).description("默认经度(东经为正)"),
1328
+ abnormalThreshold: import_koishi.Schema.number().default(3).description("作息异常判定阈值(小时)")
1329
+ }).description("自动检测(实验性)🧪"),
1330
+ import_koishi.Schema.object({
1331
+ useStorageService: import_koishi.Schema.boolean().default(true).description("使用 chatluna-storage-service 缓存图片(推荐)"),
1332
+ storageCacheHours: import_koishi.Schema.number().default(24).description("图片缓存时间(小时)"),
1333
+ logRetentionDays: import_koishi.Schema.number().default(45).description("旅行记录保留天数"),
1334
+ logPath: import_koishi.Schema.string().default("./data/pig/logs").description("本地日志存储路径(仅在不使用存储服务时生效)")
1335
+ }).description("存储设置 💾"),
1336
+ import_koishi.Schema.object({
1337
+ debug: import_koishi.Schema.boolean().default(false).description("输出详细调试日志")
1338
+ }).description("调试")
1339
+ ]);
1313
1340
 
1314
1341
  // src/index.ts
1315
1342
  var name = "my-pig-group-friends";
@@ -1339,9 +1366,17 @@ function apply(ctx, config) {
1339
1366
  if (config.useStorageService && !ctx.chatluna_storage) {
1340
1367
  ctx.logger("pig").warn("useStorageService 已启用但 chatluna_storage 服务不可用,将回退到 base64 模式");
1341
1368
  }
1342
- ctx.command("pig <user:user>", "虚拟旅行").action(async ({ session }, user) => {
1343
- if (!user) return "请指定一个用户";
1344
- const [platform, userId] = user.split(":");
1369
+ const travelLocks = /* @__PURE__ */ new Map();
1370
+ const LOCK_DURATION_MS = 60 * 1e3;
1371
+ ctx.command("pig [user:user]", "虚拟旅行").alias("猪醒").action(async ({ session }, user) => {
1372
+ let platform;
1373
+ let userId;
1374
+ if (user) {
1375
+ [platform, userId] = user.split(":");
1376
+ } else {
1377
+ platform = session.platform;
1378
+ userId = session.userId;
1379
+ }
1345
1380
  let userInfo;
1346
1381
  if (session?.userId === userId) {
1347
1382
  userInfo = {
@@ -1350,18 +1385,55 @@ function apply(ctx, config) {
1350
1385
  avatarUrl: session.author?.avatar || ""
1351
1386
  };
1352
1387
  } else {
1388
+ let avatarUrl = "";
1389
+ let username = userId;
1390
+ try {
1391
+ if (platform === "onebot") {
1392
+ avatarUrl = `https://q.qlogo.cn/headimg_dl?dst_uin=${userId}&spec=640`;
1393
+ }
1394
+ if (session.bot) {
1395
+ if (session.guildId && session.bot.getGuildMember) {
1396
+ try {
1397
+ const member = await session.bot.getGuildMember(session.guildId, userId);
1398
+ username = member.nick || member.name || member.user?.name || username;
1399
+ avatarUrl = avatarUrl || member.user?.avatar || "";
1400
+ } catch (e) {
1401
+ }
1402
+ }
1403
+ if (username === userId && session.bot.getUser) {
1404
+ try {
1405
+ const user2 = await session.bot.getUser(userId);
1406
+ username = user2.name || username;
1407
+ avatarUrl = avatarUrl || user2.avatar || "";
1408
+ } catch (e) {
1409
+ }
1410
+ }
1411
+ }
1412
+ } catch (e) {
1413
+ ctx.logger("pig").warn(`Failed to fetch metadata for user ${userId}: ${e}`);
1414
+ }
1353
1415
  userInfo = {
1354
1416
  userId,
1355
- username: userId,
1356
- avatarUrl: ""
1417
+ username,
1418
+ avatarUrl
1357
1419
  };
1358
1420
  }
1359
1421
  const result = await triggerTravelSequence(ctx, config, userInfo, platform);
1360
1422
  return formatTravelMessage(result, userId, config);
1361
1423
  });
1362
1424
  ctx.middleware(async (session, next) => {
1425
+ if (!config.experimentalAutoDetect) return next();
1363
1426
  if (!session.userId || !session.content) return next();
1364
- const now = /* @__PURE__ */ new Date();
1427
+ const lockKey = `${session.platform}:${session.userId}`;
1428
+ const now = Date.now();
1429
+ const lockTime = travelLocks.get(lockKey);
1430
+ if (lockTime && now - lockTime < LOCK_DURATION_MS) {
1431
+ if (config.debug) {
1432
+ ctx.logger("pig").debug(`用户 ${session.userId} 处于锁定期,跳过自动检测`);
1433
+ }
1434
+ return next();
1435
+ }
1436
+ const nowDate = /* @__PURE__ */ new Date();
1365
1437
  const [userState] = await ctx.database.get("pig_user_state", {
1366
1438
  userId: session.userId,
1367
1439
  platform: session.platform
@@ -1371,10 +1443,17 @@ function apply(ctx, config) {
1371
1443
  try {
1372
1444
  const sunriseInfo = await getSunriseInfo(ctx, lat, lng);
1373
1445
  const dayStart = new Date(sunriseInfo.sunrise.getTime() - 2 * 60 * 60 * 1e3);
1374
- if (now >= dayStart && (!userState?.lastWakeUp || userState.lastWakeUp < dayStart)) {
1446
+ if (nowDate >= dayStart && (!userState?.lastWakeUp || userState.lastWakeUp < dayStart)) {
1447
+ await ctx.database.upsert("pig_user_state", [{
1448
+ userId: session.userId,
1449
+ platform: session.platform,
1450
+ lastWakeUp: nowDate,
1451
+ lastSunrise: sunriseInfo.sunrise
1452
+ }], ["platform", "userId"]);
1375
1453
  if (userState?.lastWakeUp) {
1376
- const diffHours = Math.abs((now.getTime() - userState.lastWakeUp.getTime() - 24 * 60 * 60 * 1e3) / (1e3 * 60 * 60));
1454
+ const diffHours = Math.abs((nowDate.getTime() - userState.lastWakeUp.getTime() - 24 * 60 * 60 * 1e3) / (1e3 * 60 * 60));
1377
1455
  if (diffHours > config.abnormalThreshold) {
1456
+ travelLocks.set(lockKey, now);
1378
1457
  await session.send(`检测到 ${import_koishi2.segment.at(session.userId)} 作息异常(差异: ${diffHours.toFixed(1)}小时),准备虚拟旅行...`);
1379
1458
  const userInfo = {
1380
1459
  userId: session.userId,
@@ -1385,12 +1464,6 @@ function apply(ctx, config) {
1385
1464
  await session.send(formatTravelMessage(result, session.userId, config));
1386
1465
  }
1387
1466
  }
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
1467
  }
1395
1468
  } catch (e) {
1396
1469
  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.1",
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
+ }