koishi-plugin-rocom 1.0.19 → 1.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/lib/atlas-service.js +1 -1
  2. package/lib/client.d.ts +10 -0
  3. package/lib/client.js +44 -8
  4. package/lib/commands/egg.js +1 -1
  5. package/lib/commands/merchant.d.ts +105 -0
  6. package/lib/commands/merchant.js +474 -54
  7. package/lib/commands/query.d.ts +18 -0
  8. package/lib/commands/query.js +446 -37
  9. package/lib/commands/share-code.js +47 -2
  10. package/lib/commands/tools.js +1 -1
  11. package/lib/commands/wiki.js +1 -1
  12. package/lib/egg-service.js +1 -1
  13. package/lib/element-colors.d.ts +3 -0
  14. package/lib/element-colors.js +34 -0
  15. package/lib/icon-metrics.d.ts +32 -0
  16. package/lib/icon-metrics.js +255 -0
  17. package/lib/index.d.ts +5 -0
  18. package/lib/index.js +11857 -188
  19. package/lib/merchant-catalog.d.ts +47 -0
  20. package/lib/merchant-catalog.js +193 -0
  21. package/lib/merchant-price-log.d.ts +63 -0
  22. package/lib/merchant-price-log.js +187 -0
  23. package/lib/merchant-service.d.ts +17 -3
  24. package/lib/merchant-service.js +79 -11
  25. package/lib/pet-data-service.d.ts +13 -0
  26. package/lib/pet-data-service.js +111 -2
  27. package/lib/player-service.js +2 -2
  28. package/lib/ranking-service.js +1 -1
  29. package/lib/render-templates/home/style.css +15 -14
  30. package/lib/render-templates/home-pets/index.html +80 -0
  31. package/lib/render-templates/home-pets/style.css +338 -0
  32. package/lib/render-templates/pet-detail-classic/index.html +151 -0
  33. package/lib/render-templates/pet-detail-classic/style.css +503 -0
  34. package/lib/render-templates/pet-detail-classic/texture2D/a_title.png +0 -0
  35. package/lib/render-templates/pet-detail-classic/texture2D/bg.jpg +0 -0
  36. package/lib/render-templates/pet-detail-classic/texture2D/footer.png +0 -0
  37. package/lib/render-templates/pet-detail-classic/texture2D/icon.png +0 -0
  38. package/lib/render-templates/pet-detail-classic/texture2D/img_head.png +0 -0
  39. package/lib/render-templates/pet-detail-classic/texture2D/jinhua_bg.png +0 -0
  40. package/lib/render-templates/pet-detail-classic/texture2D/level_icon.png +0 -0
  41. package/lib/render-templates/pet-detail-classic/texture2D/line.png +0 -0
  42. package/lib/render-templates/pet-detail-classic/texture2D/mask_bar.png +0 -0
  43. package/lib/render-templates/pet-detail-classic/texture2D/pet_bg.png +0 -0
  44. package/lib/render-templates/pet-detail-classic/texture2D/pet_rocom.png +0 -0
  45. package/lib/render-templates/pet-detail-classic/texture2D/pet_rocom_bg.png +0 -0
  46. package/lib/render-templates/pet-detail-classic/texture2D/poro_bar.png +0 -0
  47. package/lib/render-templates/pet-detail-classic/texture2D/right_jinhua.png +0 -0
  48. package/lib/render-templates/pet-detail-classic/texture2D/skill_bg.png +0 -0
  49. package/lib/render-templates/pet-detail-classic/texture2D/skill_mask.png +0 -0
  50. package/lib/render-templates/pet-detail-classic/texture2D/star.png +0 -0
  51. package/lib/render-templates/pet-detail-classic/texture2D/star_1.png +0 -0
  52. package/lib/render-templates/pet-detail-classic/texture2D/star_8.png +0 -0
  53. package/lib/render-templates/pet-detail-classic/texture2D/star_9.png +0 -0
  54. package/lib/render-templates/pet-detail-classic/texture2D/table.png +0 -0
  55. package/lib/render-templates/pet-detail-classic/texture2D/tags.png +0 -0
  56. package/lib/render-templates/pet-detail-classic/texture2D/title.png +0 -0
  57. package/lib/render-templates/pet-detail-classic/texture2D/top_bg.png +0 -0
  58. package/lib/render-templates/pet-detail-classic/texture2D/xuancai_overlay.png +0 -0
  59. package/lib/render-templates/pet-detail-classic/texture2D/yise_overlay.png +0 -0
  60. package/lib/render-templates/pet-detail-classic/texture2D/ys_bar.png +0 -0
  61. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/10.png +0 -0
  62. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/11.png +0 -0
  63. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/12.png +0 -0
  64. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/13.png +0 -0
  65. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/14.png +0 -0
  66. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/15.png +0 -0
  67. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/16.png +0 -0
  68. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/17.png +0 -0
  69. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/18.png +0 -0
  70. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/19.png +0 -0
  71. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/2.png +0 -0
  72. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/20.png +0 -0
  73. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/23.png +0 -0
  74. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/3.png +0 -0
  75. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/4.png +0 -0
  76. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/5.png +0 -0
  77. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/6.png +0 -0
  78. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/8.png +0 -0
  79. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247/9.png +0 -0
  80. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//345/205/211.png +0 -0
  81. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//345/206/260.png +0 -0
  82. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//345/234/260.png +0 -0
  83. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//345/271/273.png +0 -0
  84. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//345/271/275.png +0 -0
  85. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//346/201/266.png +0 -0
  86. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//346/227/240.png +0 -0
  87. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//346/231/256/351/200/232.png +0 -0
  88. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//346/234/272/346/242/260.png +0 -0
  89. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//346/255/246.png +0 -0
  90. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//346/257/222.png +0 -0
  91. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//346/260/264.png +0 -0
  92. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//347/201/253.png +0 -0
  93. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//347/224/265.png +0 -0
  94. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//347/277/274.png +0 -0
  95. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//350/215/211.png +0 -0
  96. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//350/220/214.png +0 -0
  97. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//350/231/253.png +0 -0
  98. package/lib/render-templates/pet-detail-classic/texture2D//345/261/236/346/200/247//351/276/231.png +0 -0
  99. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/1.png +0 -0
  100. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/10.png +0 -0
  101. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/11.png +0 -0
  102. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/12.png +0 -0
  103. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/13.png +0 -0
  104. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/14.png +0 -0
  105. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/15.png +0 -0
  106. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/16.png +0 -0
  107. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/17.png +0 -0
  108. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/18.png +0 -0
  109. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/19.png +0 -0
  110. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/2.png +0 -0
  111. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/21.png +0 -0
  112. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/23.png +0 -0
  113. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/24.png +0 -0
  114. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/3.png +0 -0
  115. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/4.png +0 -0
  116. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/5.png +0 -0
  117. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/6.png +0 -0
  118. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/7.png +0 -0
  119. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/8.png +0 -0
  120. package/lib/render-templates/pet-detail-classic/texture2D//350/241/200/350/204/211/9.png +0 -0
  121. package/lib/render-templates/share-code-classic/assets/eit-logo.png +0 -0
  122. package/lib/render-templates/share-code-classic/assets/img_xingxingdi.png +0 -0
  123. package/lib/render-templates/share-code-classic/assets/roco_icon.png +0 -0
  124. package/lib/render-templates/share-code-classic/assets//345/255/227/344/275/223/Rocofontblack-Regular .ttf +0 -0
  125. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//345/205/211.png +0 -0
  126. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//345/206/260.png +0 -0
  127. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//345/234/260.png +0 -0
  128. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//345/271/273.png +0 -0
  129. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//345/271/275.png +0 -0
  130. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//346/201/266.png +0 -0
  131. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//346/227/240.png +0 -0
  132. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//346/231/256/351/200/232.png +0 -0
  133. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//346/234/272/346/242/260.png +0 -0
  134. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//346/255/246.png +0 -0
  135. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//346/257/222.png +0 -0
  136. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//346/260/264.png +0 -0
  137. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//347/201/253.png +0 -0
  138. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//347/224/265.png +0 -0
  139. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//347/277/274.png +0 -0
  140. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//350/215/211.png +0 -0
  141. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//350/220/214.png +0 -0
  142. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//350/231/253.png +0 -0
  143. package/lib/render-templates/share-code-classic/assets//345/256/240/347/211/251/345/261/236/346/200/247//351/276/231.png +0 -0
  144. package/lib/render-templates/share-code-classic/assets//347/201/253/346/274/206/345/215/260.png +0 -0
  145. package/lib/render-templates/share-code-classic/assets//347/253/213/347/273/230/350/203/214/346/231/257/346/241/206.png +0 -0
  146. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//345/205/211.png +0 -0
  147. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//345/206/260.png +0 -0
  148. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//345/234/260.png +0 -0
  149. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//345/245/207/345/274/202.png +0 -0
  150. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//345/271/273.png +0 -0
  151. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//345/271/275.png +0 -0
  152. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//346/201/266.png +0 -0
  153. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//346/231/256/351/200/232.png +0 -0
  154. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//346/234/272/346/242/260.png +0 -0
  155. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//346/255/246.png +0 -0
  156. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//346/257/222.png +0 -0
  157. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//346/260/264.png +0 -0
  158. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//346/261/241/346/237/223.png +0 -0
  159. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//347/201/253.png +0 -0
  160. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//347/224/265.png +0 -0
  161. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//347/277/274.png +0 -0
  162. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//350/215/211.png +0 -0
  163. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//350/220/214.png +0 -0
  164. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//350/231/253.png +0 -0
  165. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//351/246/226/351/242/206.png +0 -0
  166. package/lib/render-templates/share-code-classic/assets//350/241/200/350/204/211//351/276/231.png +0 -0
  167. package/lib/render-templates/share-code-classic/index.html +119 -0
  168. package/lib/render-templates/share-code-classic/style.css +646 -0
  169. package/lib/render-templates/yuanxing-shangren/merchant.html +479 -461
  170. package/lib/render-templates/yuanxing-shangren/today.html +407 -461
  171. package/lib/render.d.ts +3 -0
  172. package/lib/render.js +19 -3
  173. package/lib/send-image.js +10 -0
  174. package/lib/share-code-service.d.ts +36 -0
  175. package/lib/share-code-service.js +73 -0
  176. package/lib/ttf/rocom_origin.ttf +0 -0
  177. package/lib/types.d.ts +9 -1
  178. package/lib/user.d.ts +6 -0
  179. package/lib/wiki-catalog.js +1 -1
  180. package/lib/wiki-service.js +1 -1
  181. package/package.json +2 -1
  182. package/readme.md +3 -23
  183. package/lib/ttf/rocom_skill_origin.ttf +0 -0
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.extractHomeEggStatus = extractHomeEggStatus;
6
7
  exports.register = register;
7
8
  const koishi_1 = require("koishi");
8
9
  const account_1 = require("./account");
@@ -831,7 +832,7 @@ function extractHomePet(deps, raw, index, guard = false) {
831
832
  const feedRound = Number(homePet.feed_round || raw.feed_round || 0) || 0;
832
833
  const gender = Number(display.gender || raw.gender || 0) || 0;
833
834
  let mutationType = Number(display.mutation_type || raw.mutation_type || homePet.mutation_type || 0) || 0;
834
- // 上游 v3.4.0:mutation_type 缺失时按 real_speciality_ids 识别变体(103=异色、502=炫彩、两者兼有=异色炫彩)。
835
+ // mutation_type 缺失时按 real_speciality_ids 识别变体(103=异色、502=炫彩、两者兼有=异色炫彩)。
835
836
  if (!mutationType) {
836
837
  const specialityValues = [];
837
838
  for (const value of [homePet.real_speciality_ids, raw.real_speciality_ids, homePet.speciality_id, raw.speciality_id]) {
@@ -1117,13 +1118,13 @@ function buildPlayerSearchRenderData(payload, uid, cardImageUrl = '') {
1117
1118
  copyright: 'Koishi & WeGame Locke Kingdom Plugin',
1118
1119
  };
1119
1120
  }
1120
- function buildShopRenderData(payload, shopId) {
1121
+ function buildShopRenderData(payload, shopId, options = {}) {
1121
1122
  const sections = [];
1122
1123
  const detailItems = [];
1123
1124
  const summaryCards = [{ label: '商店 ID', value: shopId }];
1124
1125
  const shop = payload?.shop || {};
1125
1126
  if (Array.isArray(payload?.goods)) {
1126
- const items = (0, merchant_service_1.normalizeLiveMerchant)(payload);
1127
+ const items = (0, merchant_service_1.normalizeLiveMerchant)(payload, Date.now(), { assetBaseUrl: options.assetBaseUrl });
1127
1128
  summaryCards.push({ label: '商品数量', value: String(items.length) });
1128
1129
  const shopName = String(shop.name || shop.title || '').trim();
1129
1130
  if (shopName)
@@ -1293,7 +1294,7 @@ const HOME_SUB_TEXT = {
1293
1294
  needUidHint: '请提供玩家 UID,或先完成绑定后再订阅家园生蛋。',
1294
1295
  },
1295
1296
  };
1296
- async function subscribeHome(deps, session, uid, kind) {
1297
+ async function subscribeHome(deps, session, uid, kind, mentionAll = null) {
1297
1298
  const target = sessionTarget(session);
1298
1299
  if (!target.userId && !canManageGroupSubscription(deps, session))
1299
1300
  return '此指令仅限群管理员或机器人管理员使用。';
@@ -1315,10 +1316,275 @@ async function subscribeHome(deps, session, uid, kind) {
1315
1316
  guild_id: session?.guildId || '',
1316
1317
  user_id: target.userId,
1317
1318
  updated_by: session?.userId || '',
1319
+ // 群订阅支持命中后 @全体;未显式指定时沿用上次设置。
1320
+ mention_all: target.userId ? false : (mentionAll ?? existing?.mention_all ?? false),
1318
1321
  notify_state: existing?.notify_state || {},
1319
1322
  updated_at: Math.floor(Date.now() / 1000),
1320
1323
  });
1321
- return `已订阅 UID ${targetUid} ${HOME_SUB_TEXT[kind].subscribedHint}`;
1324
+ const finalMentionAll = target.userId ? false : (mentionAll ?? existing?.mention_all ?? false);
1325
+ const mentionHint = target.userId ? '' : (finalMentionAll ? '命中后会尝试 @全体。' : '命中后不会 @全体。');
1326
+ return [`已订阅 UID ${targetUid} ${HOME_SUB_TEXT[kind].subscribedHint}`, mentionHint].filter(Boolean).join('');
1327
+ }
1328
+ // 订阅家园 的 @全体 写法与 UID 解析。
1329
+ const HOME_MENTION_ALL_ALIASES = new Set(['@全体', '全体', '@所有人', 'at全体', 'at所有人']);
1330
+ function parseHomeSubscribeArgs(args) {
1331
+ const tokens = String(args || '').split(/\s+/).filter(Boolean);
1332
+ let mentionAll = null;
1333
+ const uids = [];
1334
+ for (const token of tokens) {
1335
+ if (HOME_MENTION_ALL_ALIASES.has(token.toLowerCase())) {
1336
+ mentionAll = true;
1337
+ continue;
1338
+ }
1339
+ uids.push(token);
1340
+ }
1341
+ return { mentionAll, uid: uids[0] || '' };
1342
+ }
1343
+ function pickEggStatusPayload(payload) {
1344
+ const candidates = [payload?.data, payload?.result, payload?.data?.data, payload?.result?.data, payload];
1345
+ for (const candidate of candidates) {
1346
+ if (candidate && typeof candidate === 'object' && !Array.isArray(candidate))
1347
+ return candidate;
1348
+ }
1349
+ return {};
1350
+ }
1351
+ function extractHomeEggPet(item, nowTs) {
1352
+ const petInfo = item?.home_pet_info && typeof item.home_pet_info === 'object' ? item.home_pet_info : item;
1353
+ const petId = petInfo?.pet_cfg_id || petInfo?.pet_id || item?.pet_cfg_id || item?.pet_id;
1354
+ const name = String(petInfo?.name || petInfo?.pet_name || item?.name || item?.pet_name || '').trim();
1355
+ if (!Number(petId) && !name)
1356
+ return null;
1357
+ const hasEgg = Boolean(item?.have_egg ?? petInfo?.have_egg);
1358
+ const predictedEggTime = normalizeEpochSeconds(item?.predicted_egg_time ?? petInfo?.predicted_egg_time ?? item?.egg_ready_time);
1359
+ const ready = hasEgg || (predictedEggTime > 0 && nowTs >= predictedEggTime);
1360
+ const remaining = ready ? '' : (predictedEggTime > 0 ? formatEggRemaining(predictedEggTime, nowTs) : '未知');
1361
+ return {
1362
+ petId: String(petId || ''),
1363
+ name: name || `精灵 ${petId || ''}`,
1364
+ ready,
1365
+ remaining,
1366
+ predictedEggTime,
1367
+ hasEgg,
1368
+ };
1369
+ }
1370
+ /**
1371
+ * 从 home/eggs 轻量接口响应中提取蛋状态。
1372
+ * 接口仍在开发中,这里对多种可能的字段名做兼容。
1373
+ */
1374
+ function extractHomeEggStatus(payload) {
1375
+ const data = pickEggStatusPayload(payload);
1376
+ const nowTs = Math.floor(Date.now() / 1000);
1377
+ const eggLists = [
1378
+ data?.eggs,
1379
+ data?.egg_list,
1380
+ data?.egg_info,
1381
+ data?.home_eggs,
1382
+ data?.home_egg_list,
1383
+ data?.home_pets,
1384
+ data?.pet_list,
1385
+ ].filter(Array.isArray);
1386
+ const pets = [];
1387
+ for (const list of eggLists) {
1388
+ for (const item of list) {
1389
+ const pet = extractHomeEggPet(item, nowTs);
1390
+ if (pet)
1391
+ pets.push(pet);
1392
+ }
1393
+ }
1394
+ const readyCount = pets.length > 0
1395
+ ? pets.filter(pet => pet.ready).length
1396
+ : Number(data?.ready_egg_count ?? data?.readyEggCount ?? data?.egg_ready_count ?? 0) || 0;
1397
+ const totalEggs = pets.length > 0
1398
+ ? pets.length
1399
+ : Number(data?.total_egg_count ?? data?.totalEggCount ?? data?.egg_count ?? 0) || 0;
1400
+ return { readyCount, totalEggs, pets };
1401
+ }
1402
+ // 与全量家园信息的蛋状态口径一致:只统计参与生蛋的精灵(有蛋或已排期)。
1403
+ function homeEggSubscriptionState(payload) {
1404
+ const status = extractHomeEggStatus(payload);
1405
+ const items = status.pets.filter(pet => pet.hasEgg || pet.predictedEggTime > 0);
1406
+ const readyItems = items.filter(pet => pet.ready);
1407
+ return { items, readyItems, names: readyItems.map(pet => pet.name) };
1408
+ }
1409
+ function buildHomeEggStatusText(uid, status) {
1410
+ const lines = [
1411
+ `家园蛋状态 - UID ${uid}`,
1412
+ `可收蛋精灵:${status.readyCount} 只 / 共检测 ${status.totalEggs} 只`,
1413
+ ];
1414
+ const tracked = status.pets.filter(pet => pet.hasEgg || pet.predictedEggTime > 0);
1415
+ if (tracked.length > 0) {
1416
+ lines.push('');
1417
+ for (const pet of tracked.slice(0, 30)) {
1418
+ lines.push(`${pet.name} ${pet.ready ? '✅ 可收蛋' : `⏳ ${pet.remaining}`}`);
1419
+ }
1420
+ if (tracked.length > 30)
1421
+ lines.push(`…等共 ${tracked.length} 只`);
1422
+ }
1423
+ lines.push('');
1424
+ lines.push(`更新时间:${new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}`);
1425
+ return lines.join('\n');
1426
+ }
1427
+ // 家园精灵一览(home/pet-data),汇总体重、声音、变异与生蛋状态。
1428
+ function collectHomePetsViewSources(homeInfo, payload) {
1429
+ const containers = [homeInfo?.friend_cell_home_brief_info, homeCellInfo(homeInfo), homeInfo?.cell_info, homeInfo, payload?.friend_cell_home_brief_info];
1430
+ const result = [];
1431
+ const seen = new Set();
1432
+ for (const container of containers) {
1433
+ if (!container || typeof container !== 'object' || !Array.isArray(container.home_pets))
1434
+ continue;
1435
+ for (const item of container.home_pets) {
1436
+ const info = item?.home_pet_info && typeof item.home_pet_info === 'object' ? item.home_pet_info : item;
1437
+ const key = String(info?.pet_gid || info?.furniture_guid || `${info?.pet_cfg_id || ''}:${info?.name || ''}`);
1438
+ if (key && seen.has(key))
1439
+ continue;
1440
+ if (key)
1441
+ seen.add(key);
1442
+ result.push(item);
1443
+ }
1444
+ }
1445
+ return result;
1446
+ }
1447
+ function homePetsViewPetFromNpc(raw) {
1448
+ const npcPet = raw?.npc_pet && typeof raw.npc_pet === 'object' ? raw.npc_pet : {};
1449
+ const pet = npcPet?.pet && typeof npcPet.pet === 'object' ? npcPet.pet : (raw?.pet && typeof raw.pet === 'object' ? raw.pet : raw?.display_info);
1450
+ const petId = raw?.pet_cfg_id || raw?.pet_id || pet?.base_conf_id || pet?.pet_id || pet?.id;
1451
+ return {
1452
+ home_pet_info: {
1453
+ pet_gid: raw?.pet_gid || pet?.pet_gid,
1454
+ npc_id: raw?.npc_id || raw?.npcId,
1455
+ pet_cfg_id: petId,
1456
+ furniture_guid: raw?.furniture_guid,
1457
+ name: raw?.name || pet?.name,
1458
+ speciality_id: raw?.speciality_id || pet?.speciality_id,
1459
+ real_speciality_ids: raw?.real_speciality_ids || pet?.real_speciality_ids || [],
1460
+ status: raw?.status,
1461
+ },
1462
+ display_info: pet,
1463
+ };
1464
+ }
1465
+ function isHomePetsViewGuard(homePetInfo, displayInfo, pet) {
1466
+ const status = String(homePetInfo?.status ?? pet?.status ?? displayInfo?.status ?? '').toLowerCase();
1467
+ const zeroCfgWithName = String(homePetInfo?.pet_cfg_id || '0') === '0'
1468
+ && Boolean(String(homePetInfo?.name || homePetInfo?.pet_name || '').trim());
1469
+ return zeroCfgWithName
1470
+ || Boolean(homePetInfo?.is_guard || homePetInfo?.guard || pet?.is_guard || pet?.guard || displayInfo?.is_guard || displayInfo?.guard)
1471
+ || status === '2' || status === 'guard';
1472
+ }
1473
+ // ingame 数据体重可能以克或千克记录:大数值按克换算成千克展示。
1474
+ function homePetsWeightText(value) {
1475
+ const num = Number(value);
1476
+ if (!Number.isFinite(num) || num <= 0)
1477
+ return '--';
1478
+ const kg = num >= 1000 ? num / 1000 : num;
1479
+ const text = kg.toFixed(2).replace(/0+$/, '').replace(/\.$/, '');
1480
+ return `${text}kg`;
1481
+ }
1482
+ function buildHomePetsViewData(deps, res, uid) {
1483
+ const raw = res || {};
1484
+ const payload = raw.result && typeof raw.result === 'object' ? raw.result : raw;
1485
+ const homeInfo = homeInfoPayload(raw);
1486
+ const homePets = collectHomePetsViewSources(homeInfo, payload);
1487
+ const npcPets = Array.isArray(payload.npc_pets) ? payload.npc_pets : [];
1488
+ const npcMap = new Map();
1489
+ for (const entry of npcPets) {
1490
+ const npcPet = entry?.npc_pet && typeof entry.npc_pet === 'object' ? entry.npc_pet : {};
1491
+ const pet = npcPet?.pet && typeof npcPet.pet === 'object' ? npcPet.pet : entry?.pet;
1492
+ const gid = String(entry?.pet_gid || entry?.petGid || npcPet?.pet_gid || pet?.pet_gid || '').trim();
1493
+ if (gid)
1494
+ npcMap.set(gid, entry);
1495
+ }
1496
+ const nowTs = Math.floor(Date.now() / 1000);
1497
+ const petSources = homePets.length > 0 ? homePets : npcPets.map(entry => homePetsViewPetFromNpc(entry));
1498
+ const pets = [];
1499
+ for (const hp of petSources) {
1500
+ const homePetInfo = hp?.home_pet_info && typeof hp.home_pet_info === 'object' ? hp.home_pet_info : hp;
1501
+ const displayInfo = hp?.display_info && typeof hp.display_info === 'object' ? hp.display_info : {};
1502
+ const petGid = String(homePetInfo?.pet_gid || displayInfo?.pet_gid || '').trim();
1503
+ const npcRaw = npcMap.get(petGid) || {};
1504
+ const npcPetObj = npcRaw?.npc_pet && typeof npcRaw.npc_pet === 'object' ? npcRaw.npc_pet : {};
1505
+ const npcFullPet = npcPetObj?.pet && typeof npcPetObj.pet === 'object' ? npcPetObj.pet
1506
+ : (npcRaw?.pet && typeof npcRaw.pet === 'object' ? npcRaw.pet : npcRaw?.display_info);
1507
+ const npcStatus = String(npcRaw?.status || '');
1508
+ const hasFull = npcFullPet && typeof npcFullPet === 'object' && Object.keys(npcFullPet).length > 0 && npcStatus !== 'error';
1509
+ const pet = hasFull ? npcFullPet : displayInfo;
1510
+ const petId = pet?.base_conf_id || pet?.pet_id || pet?.id || displayInfo?.base_conf_id || homePetInfo?.pet_cfg_id;
1511
+ if (isHomePetsViewGuard(homePetInfo, displayInfo, pet) || !Number(petId))
1512
+ continue;
1513
+ const name = String(pet?.name || homePetInfo?.name || homePetInfo?.pet_name || `精灵 ${petId}`).trim();
1514
+ const gender = Number(pet?.gender ?? displayInfo?.gender ?? 0) || 0;
1515
+ const genderText = gender === 1 ? '♂ 雄性' : gender === 2 ? '♀ 雌性' : '未知';
1516
+ let mutationType = Number(pet?.mutation_type ?? displayInfo?.mutation_type ?? 0) || 0;
1517
+ if (!mutationType) {
1518
+ // 与 extractHomePet 相同的变体兜底(real_speciality_ids 103=异色、502=炫彩)。
1519
+ const specialityValues = [];
1520
+ for (const value of [homePetInfo.real_speciality_ids, npcRaw.real_speciality_ids, homePetInfo.speciality_id, npcRaw.speciality_id]) {
1521
+ if (Array.isArray(value))
1522
+ specialityValues.push(...value);
1523
+ else if (value !== undefined && value !== null && value !== '')
1524
+ specialityValues.push(value);
1525
+ }
1526
+ const specialityIds = new Set(specialityValues.map(value => String(value).trim()).filter(Boolean));
1527
+ if (specialityIds.has('103') && specialityIds.has('502'))
1528
+ mutationType = 9;
1529
+ else if (specialityIds.has('103'))
1530
+ mutationType = 1;
1531
+ else if (specialityIds.has('502'))
1532
+ mutationType = 8;
1533
+ }
1534
+ const variantText = String(pet?.mutation_name || displayInfo?.mutation_name || '')
1535
+ || (mutationType === 9 ? '异色炫彩' : mutationType === 1 ? '异色' : mutationType === 8 ? '炫彩' : '');
1536
+ const hasEgg = Boolean(hp?.have_egg ?? pet?.have_egg);
1537
+ const predictedEggTime = normalizeEpochSeconds(hp?.predicted_egg_time ?? pet?.predicted_egg_time);
1538
+ const eggReady = hasEgg || (predictedEggTime > 0 && nowTs >= predictedEggTime);
1539
+ const eggText = eggReady ? '可收蛋' : (predictedEggTime > 0 ? `${formatEggRemaining(predictedEggTime, nowTs)}后生蛋` : '');
1540
+ pets.push({
1541
+ id: String(petId || ''),
1542
+ name,
1543
+ level: pet?.level ?? displayInfo?.level ?? homePetInfo?.level ?? '--',
1544
+ genderText,
1545
+ weightText: homePetsWeightText(pet?.weight ?? displayInfo?.weight ?? homePetInfo?.weight ?? pet?.pet_weight),
1546
+ voiceText: (0, pet_data_service_1.petDataVoiceInfo)(pet?.voice ?? displayInfo?.voice).value,
1547
+ variantText,
1548
+ eggReady,
1549
+ eggText,
1550
+ iconUrl: homePetIcon(petId, pet?.pet_icon_url || displayInfo?.pet_icon_url || npcRaw?.icon_url || ''),
1551
+ starIconUrl: [1, 8, 9].includes(mutationType)
1552
+ ? deps.renderer.resourceUrl(`render-templates/home/img/rocomuid/star_${mutationType}.png`)
1553
+ : '',
1554
+ });
1555
+ }
1556
+ pets.sort((a, b) => (a.eggReady === b.eggReady ? 0 : (a.eggReady ? -1 : 1)));
1557
+ const brief = homeBriefInfo(homeInfo);
1558
+ const homeName = String(brief?.home_name || brief?.name || payload?.player_info?.name || '').trim() || '洛克玩家';
1559
+ return {
1560
+ title: '家园精灵',
1561
+ homeName,
1562
+ uid,
1563
+ pets,
1564
+ total: pets.length,
1565
+ eggCount: pets.filter(pet => pet.eggReady).length,
1566
+ emptyText: '未获取到家园精灵信息。请确认目标玩家在线、家园可访问,或稍后重试。',
1567
+ updatedAt: new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }),
1568
+ };
1569
+ }
1570
+ function buildHomePetsViewText(data) {
1571
+ const lines = [
1572
+ `【家园精灵】UID ${data.uid}(${data.homeName})`,
1573
+ `共 ${data.total} 只精灵,可收蛋 ${data.eggCount} 只`,
1574
+ ];
1575
+ for (const pet of (data.pets || []).slice(0, 12)) {
1576
+ const bits = [pet.name, `Lv.${pet.level}`, pet.genderText, pet.weightText, pet.voiceText];
1577
+ if (pet.variantText)
1578
+ bits.push(pet.variantText);
1579
+ if (pet.eggText)
1580
+ bits.push(pet.eggText);
1581
+ lines.push(`- ${bits.join(' ')}`);
1582
+ }
1583
+ if ((data.pets || []).length > 12)
1584
+ lines.push(`…等共 ${data.pets.length} 只`);
1585
+ if (!data.pets?.length)
1586
+ lines.push(data.emptyText);
1587
+ return lines.join('\n');
1322
1588
  }
1323
1589
  function homeSubscriptionState(data, kind) {
1324
1590
  if (kind === 'garden') {
@@ -1349,27 +1615,49 @@ function homeSubscriptionMessage(uid, kind, level, totalCount, readyItems, names
1349
1615
  }
1350
1616
  async function checkHomeSubscriptions(deps, signal) {
1351
1617
  const subs = deps.homeSubMgr.getAll();
1352
- const cache = new Map();
1618
+ const fullCache = new Map();
1619
+ const eggsCache = new Map();
1620
+ const nowSec = Math.floor(Date.now() / 1000);
1353
1621
  let checkedCount = 0;
1354
1622
  let pushedCount = 0;
1623
+ const fetchFull = async (uid) => {
1624
+ if (!fullCache.has(uid)) {
1625
+ fullCache.set(uid, await deps.client.ingameHomeInfo(deps.ctx, uid, { timeoutMs: 30000, signal }).catch(() => null));
1626
+ }
1627
+ return fullCache.get(uid);
1628
+ };
1355
1629
  for (const [key, sub] of Object.entries(subs)) {
1356
1630
  if (signal?.aborted)
1357
1631
  break;
1358
1632
  if (!sub.uid || !['garden', 'inspiration', 'egg'].includes(sub.kind))
1359
1633
  continue;
1360
1634
  checkedCount++;
1361
- if (!cache.has(sub.uid)) {
1362
- cache.set(sub.uid, await deps.client.ingameHomeInfo(deps.ctx, sub.uid, { timeoutMs: 30000, signal }));
1635
+ const modified = () => JSON.stringify(deps.homeSubMgr.getAll()[key]) !== JSON.stringify(sub);
1636
+ let state = null;
1637
+ if (sub.kind === 'egg') {
1638
+ // 蛋状态订阅走轻量 home/eggs 接口,降低轮询开销。
1639
+ if (!eggsCache.has(sub.uid)) {
1640
+ eggsCache.set(sub.uid, await deps.client.ingameHomeEggs(deps.ctx, sub.uid, { timeoutMs: 30000, signal }).catch(() => null));
1641
+ }
1642
+ if (signal?.aborted)
1643
+ break;
1644
+ if (modified())
1645
+ continue;
1646
+ const payload = eggsCache.get(sub.uid);
1647
+ if (payload)
1648
+ state = homeEggSubscriptionState(payload);
1363
1649
  }
1364
- if (signal?.aborted)
1365
- break;
1366
- if (JSON.stringify(deps.homeSubMgr.getAll()[key]) !== JSON.stringify(sub))
1367
- continue;
1368
- const res = cache.get(sub.uid);
1369
- if (!res)
1370
- continue;
1371
- const data = buildHomeRenderData(deps, res, sub.uid);
1372
- const { items, readyItems, names } = homeSubscriptionState(data, sub.kind);
1650
+ if (!state) {
1651
+ const res = await fetchFull(sub.uid);
1652
+ if (signal?.aborted)
1653
+ break;
1654
+ if (modified())
1655
+ continue;
1656
+ if (!res)
1657
+ continue;
1658
+ state = homeSubscriptionState(buildHomeRenderData(deps, res, sub.uid), sub.kind);
1659
+ }
1660
+ const { items, readyItems, names } = state;
1373
1661
  const totalCount = items.length;
1374
1662
  if (totalCount <= 0)
1375
1663
  continue;
@@ -1388,7 +1676,7 @@ async function checkHomeSubscriptions(deps, signal) {
1388
1676
  notifyState.all = false;
1389
1677
  }
1390
1678
  if (!pushLevels.length) {
1391
- deps.homeSubMgr.upsert(key, { ...sub, notify_state: notifyState });
1679
+ deps.homeSubMgr.upsert(key, { ...sub, notify_state: notifyState, last_check_time: nowSec });
1392
1680
  continue;
1393
1681
  }
1394
1682
  const owned = deps.userMgr.getUserBindings(sub.updated_by).find(binding => binding.role_id === sub.uid);
@@ -1396,13 +1684,18 @@ async function checkHomeSubscriptions(deps, signal) {
1396
1684
  const notifyUser = owned && sub.notify_user_id === sub.updated_by ? sub.notify_user_id : '';
1397
1685
  const messages = pushLevels.map(level => homeSubscriptionMessage(label, sub.kind, level, totalCount, readyItems, names));
1398
1686
  try {
1687
+ const body = messages.join('\n\n');
1688
+ // 群订阅 mention_all 开启时命中后 @全体。
1689
+ const content = !sub.user_id && sub.mention_all
1690
+ ? [(0, koishi_1.h)('at', { type: 'all' }), koishi_1.h.text('\n' + body)]
1691
+ : (!sub.user_id && notifyUser ? [koishi_1.h.at(notifyUser), koishi_1.h.text('\n' + body)] : body);
1399
1692
  const sent = await (0, subscription_send_1.sendScheduledMessage)(deps.ctx, {
1400
1693
  platform: sub.platform,
1401
1694
  channelId: sub.channel_id || sub.guild_id || sub.user_id || '',
1402
1695
  guildId: sub.guild_id || '',
1403
1696
  userId: sub.user_id || '',
1404
1697
  selfId: sub.self_id || '',
1405
- }, !sub.user_id && notifyUser ? [koishi_1.h.at(notifyUser), koishi_1.h.text('\n' + messages.join('\n\n'))] : messages.join('\n\n'), signal);
1698
+ }, content, signal);
1406
1699
  if (!sent || signal?.aborted || JSON.stringify(deps.homeSubMgr.getAll()[key]) !== JSON.stringify(sub))
1407
1700
  continue;
1408
1701
  }
@@ -1413,7 +1706,7 @@ async function checkHomeSubscriptions(deps, signal) {
1413
1706
  for (const level of pushLevels)
1414
1707
  notifyState[level] = true;
1415
1708
  pushedCount += pushLevels.length;
1416
- deps.homeSubMgr.upsert(key, { ...sub, notify_state: notifyState, last_push_time: Math.floor(Date.now() / 1000) });
1709
+ deps.homeSubMgr.upsert(key, { ...sub, notify_state: notifyState, last_push_time: Math.floor(Date.now() / 1000), last_check_time: nowSec });
1417
1710
  }
1418
1711
  return { subscriptions: Object.keys(subs).length, checked: checkedCount, pushed: pushedCount };
1419
1712
  }
@@ -1580,7 +1873,7 @@ function register(deps) {
1580
1873
  profileStatusText,
1581
1874
  profileStatusClass: profileStatusText === '是' ? 'online' : 'offline',
1582
1875
  commandHint: '洛克.背包 <筛选> <页码> | 洛克.战绩 <页码> | 洛克 查看菜单',
1583
- copyright: 'AstrBot & WeGame Locke Kingdom Plugin',
1876
+ copyright: 'Koishi & WeGame Roco Kingdom Plugin',
1584
1877
  };
1585
1878
  if (recentBattle) {
1586
1879
  data.hasBattleData = true;
@@ -1594,7 +1887,7 @@ function register(deps) {
1594
1887
  icon: pet.pet_img_url?.replace('/image.png', '/icon.png') || '',
1595
1888
  }));
1596
1889
  }
1597
- const fallback = `【${role.name}的档案】Lv.${role.level} UID:${role.id}\n评分:${ev.score || '0'} 收藏:${cl.current_collection_count || 0}/${cl.total_collection_count || 0}`;
1890
+ const fallback = `【${role.name || '洛克玩家'}的档案】Lv.${role.level || '--'} UID:${role.id || '--'}\n评分:${ev.score || '0'} 收藏:${cl.current_collection_count || 0}/${cl.total_collection_count || 0}`;
1598
1891
  await sendImage(deps, session, 'personal-card', data, fallback);
1599
1892
  });
1600
1893
  ctx.command('洛克').subcommand('.战绩 [page:number]', '查看对战战绩')
@@ -1634,7 +1927,7 @@ function register(deps) {
1634
1927
  });
1635
1928
  const data = {
1636
1929
  userAvatarDisplay: role.avatar_url || '',
1637
- userName: role.name,
1930
+ userName: role.name || '洛克玩家',
1638
1931
  userLevel: role.level || 1,
1639
1932
  userUid: role.id || '',
1640
1933
  winRate: `${bo.win_rate || 0}%`,
@@ -1643,7 +1936,7 @@ function register(deps) {
1643
1936
  commandHint: '洛克.战绩 <页码>',
1644
1937
  copyright: 'Koishi & WeGame 洛克王国插件',
1645
1938
  };
1646
- await sendImage(deps, session, 'record', data, `【${role.name}的战绩】胜率:${bo.win_rate || 0}% 场次:${bo.total_match || 0}`);
1939
+ await sendImage(deps, session, 'record', data, `【${role.name || '洛克玩家'}的战绩】胜率:${bo.win_rate || 0}% 场次:${bo.total_match || 0}`);
1647
1940
  });
1648
1941
  ctx.command('洛克').subcommand('.背包 [arg1:string] [arg2:string]', '查看精灵背包')
1649
1942
  .alias('洛克背包')
@@ -1935,7 +2228,9 @@ function register(deps) {
1935
2228
  ].join('\n');
1936
2229
  }
1937
2230
  const lowBandwidth = Boolean(deps.config.lowBandwidthMode);
1938
- // Wiki 补全(技能详情/精灵体重范围);低带宽模式跳过技能图标但仍取技能名。
2231
+ // classic 样式走 pet-detail-classic 模板(立绘 + 属性表 + 技能栏),
2232
+ // 不需要精灵体重范围(sizeLookup)。
2233
+ const useClassic = deps.config.homeDetailUiStyle !== 'panel';
1939
2234
  const [optionsPayload, skillLookup, sizeLookup] = await Promise.all([
1940
2235
  deps.wikiService.getOptionsPayload().catch(() => ({})),
1941
2236
  (async () => {
@@ -1949,6 +2244,8 @@ function register(deps) {
1949
2244
  return lookup;
1950
2245
  })(),
1951
2246
  (async () => {
2247
+ if (useClassic)
2248
+ return {};
1952
2249
  const lookup = {};
1953
2250
  const ids = (0, pet_data_service_1.petDataPetIdsFromPayload)(res);
1954
2251
  await Promise.all(ids.map(async (id) => {
@@ -1959,7 +2256,16 @@ function register(deps) {
1959
2256
  return lookup;
1960
2257
  })(),
1961
2258
  ]);
1962
- const data = (0, pet_data_service_1.buildPetDataRenderData)(res, targetUid || '当前绑定', {
2259
+ const classicData = useClassic
2260
+ ? (0, pet_data_service_1.buildPetDetailClassicView)(res, targetUid || '当前绑定', {
2261
+ optionsPayload,
2262
+ skillLookup,
2263
+ baseUrl: deps.config.apiBaseUrl.replace(/\/$/, ''),
2264
+ loadSkillIcons: !lowBandwidth,
2265
+ variantIconPath: mutationType => `render-templates/pet-detail-classic/texture2D/star_${mutationType}.png`,
2266
+ })
2267
+ : null;
2268
+ const data = classicData || (0, pet_data_service_1.buildPetDataRenderData)(res, targetUid || '当前绑定', {
1963
2269
  optionsPayload,
1964
2270
  skillLookup,
1965
2271
  sizeLookup,
@@ -1968,22 +2274,95 @@ function register(deps) {
1968
2274
  baseUrl: deps.config.apiBaseUrl.replace(/\/$/, ''),
1969
2275
  });
1970
2276
  const fallbackLines = [
1971
- `家园详情 - UID ${data.uid}(${data.onlineText})`,
1972
- data.notice,
2277
+ `家园详情 - UID ${data.uid}${data.onlineText ? `(${data.onlineText})` : ''}`,
2278
+ data.notice || '',
1973
2279
  ];
1974
2280
  for (const pet of (data.pets || []).slice(0, 8)) {
1975
- fallbackLines.push(`${pet.index}. ${pet.name} Lv.${pet.level} #${pet.baseId} ${pet.variantText} 分贝 ${pet.voiceText || '--'}`);
2281
+ const bits = [
2282
+ pet.index ? `${pet.index}.` : '',
2283
+ `${pet.name} Lv.${pet.level}`,
2284
+ pet.variantText,
2285
+ `分贝 ${pet.voiceText || '--'}`,
2286
+ ].filter(Boolean);
2287
+ fallbackLines.push(bits.join(' '));
1976
2288
  }
1977
2289
  if (!data.pets?.length)
1978
2290
  fallbackLines.push(data.emptyText);
1979
2291
  fallbackLines.push('');
1980
2292
  fallbackLines.push('若服务器带宽过小导致生成超时,请在配置项打开低带宽模式。');
1981
2293
  fallbackLines.push('低带宽模式开启后,家园详情将不再加载技能图标。');
1982
- await sendImage(deps, session, 'pet-data', data, fallbackLines.join('\n'), {
2294
+ await sendImage(deps, session, classicData ? 'pet-detail-classic' : 'pet-data', data, fallbackLines.join('\n'), {
1983
2295
  ...(lowBandwidth ? render_1.lowBandwidthRenderOptions : {}),
1984
2296
  timeoutMs: deps.config.renderTimeout || 30000,
1985
2297
  });
1986
2298
  });
2299
+ // 家园精灵一览(home/pet-data),体重、声音、变异与生蛋状态。
2300
+ ctx.command('洛克').subcommand('.家园精灵 [uid:string]', '查看家园精灵列表(体重、声音、生蛋状态)')
2301
+ .alias('洛克家园精灵')
2302
+ .alias('家园精灵')
2303
+ .action(async ({ session }, uid = '') => {
2304
+ let targetUid = String(uid || '').trim();
2305
+ if (!targetUid) {
2306
+ const binding = deps.userMgr.getPrimaryBinding(session.userId);
2307
+ targetUid = String(binding?.role_id || '');
2308
+ }
2309
+ const fwToken = await (0, account_1.getPrimaryToken)(deps, session.userId);
2310
+ if (!targetUid && !fwToken)
2311
+ return '请提供玩家 UID,或先完成绑定后使用 洛克.家园精灵。';
2312
+ let queuedNotified = false;
2313
+ const res = await client.ingameHomePetData(ctx, targetUid, {
2314
+ auth: { fwToken, userIdentifier: session.userId },
2315
+ waitMs: deps.config.homeQueryWaitMs,
2316
+ intervalMs: deps.config.homeQueryPollIntervalMs,
2317
+ timeoutMs: deps.config.homeQueryTimeoutMs,
2318
+ onQueued: async () => {
2319
+ if (queuedNotified)
2320
+ return;
2321
+ queuedNotified = true;
2322
+ await session?.send?.(`UID ${targetUid} 的家园精灵查询已进入队列,正在等待游戏侧返回,请稍候…`);
2323
+ },
2324
+ });
2325
+ if (!res) {
2326
+ return [
2327
+ `家园精灵查询失败:${client.getLastErrorBrief()}`,
2328
+ '提示:该接口需要目标玩家在线,且目标家园可访问。',
2329
+ ].join('\n');
2330
+ }
2331
+ const data = buildHomePetsViewData(deps, res, targetUid || '当前绑定');
2332
+ await sendImage(deps, session, 'home-pets', data, buildHomePetsViewText(data), {
2333
+ timeoutMs: deps.config.renderTimeout || 30000,
2334
+ });
2335
+ });
2336
+ // 立即查询指定 UID 的家园蛋状态。
2337
+ ctx.command('洛克').subcommand('.家园状态 [uid:string]', '查询家园蛋状态(可收蛋精灵与倒计时)')
2338
+ .alias('洛克家园状态')
2339
+ .alias('家园状态')
2340
+ .action(async ({ session }, uid = '') => {
2341
+ let targetUid = String(uid || '').trim();
2342
+ if (!targetUid) {
2343
+ const binding = deps.userMgr.getPrimaryBinding(session.userId);
2344
+ targetUid = String(binding?.role_id || '');
2345
+ }
2346
+ const fwToken = await (0, account_1.getPrimaryToken)(deps, session.userId);
2347
+ if (!targetUid && !fwToken)
2348
+ return `请提供玩家 UID,或先完成绑定后使用 洛克.家园状态。`;
2349
+ let queuedNotified = false;
2350
+ const res = await client.ingameHomeEggs(ctx, targetUid, {
2351
+ auth: { fwToken, userIdentifier: session.userId },
2352
+ waitMs: deps.config.homeQueryWaitMs,
2353
+ intervalMs: deps.config.homeQueryPollIntervalMs,
2354
+ timeoutMs: deps.config.homeQueryTimeoutMs,
2355
+ onQueued: async () => {
2356
+ if (queuedNotified)
2357
+ return;
2358
+ queuedNotified = true;
2359
+ await session?.send?.(`UID ${targetUid} 的家园蛋状态查询已进入队列,正在等待游戏侧返回,请稍候…`);
2360
+ },
2361
+ });
2362
+ if (!res)
2363
+ return `家园蛋状态查询失败:${client.getLastErrorBrief()}`;
2364
+ return buildHomeEggStatusText(targetUid, extractHomeEggStatus(res));
2365
+ });
1987
2366
  ctx.command('洛克').subcommand('.刷新面板 [uid:string]', '刷新指定 UID 的精灵面板缓存')
1988
2367
  .alias('洛克刷新面板')
1989
2368
  .alias('刷新面板')
@@ -2074,7 +2453,7 @@ function register(deps) {
2074
2453
  const res = await client.ingameMerchantInfo(ctx, targetShopId);
2075
2454
  if (!res)
2076
2455
  return `商店查询失败:${client.getLastErrorBrief()}`;
2077
- await sendImage(deps, session, 'ingame-shop', buildShopRenderData(res, targetShopId), `【洛克商店】shop_id=${targetShopId}`);
2456
+ await sendImage(deps, session, 'ingame-shop', buildShopRenderData(res, targetShopId, { assetBaseUrl: client.wikiAssetBaseUrl }), `【洛克商店】shop_id=${targetShopId}`);
2078
2457
  });
2079
2458
  ctx.command('洛克').subcommand('.好友关系 <userIds:string>', '查询好友关系')
2080
2459
  .alias('洛克好友关系')
@@ -2089,15 +2468,45 @@ function register(deps) {
2089
2468
  return `好友关系查询失败:${client.getLastErrorBrief()}`;
2090
2469
  await sendImage(deps, session, 'friendship', buildFriendshipRenderData(res, userIds), `【好友关系】${userIds}`);
2091
2470
  });
2092
- ctx.command('订阅家园菜园 [uid:string]', '订阅指定 UID 的家园菜园成熟提醒')
2471
+ ctx.command('订阅家园菜园 [args:text]', '订阅指定 UID 的家园菜园成熟提醒')
2472
+ .userFields(['authority'])
2473
+ .action(async ({ session }, args = '') => {
2474
+ const parsed = parseHomeSubscribeArgs(args);
2475
+ return subscribeHome(deps, session, parsed.uid, 'garden', parsed.mentionAll);
2476
+ });
2477
+ ctx.command('订阅家园灵感 [args:text]', '订阅指定 UID 的家园精灵灵感完成提醒')
2093
2478
  .userFields(['authority'])
2094
- .action(async ({ session }, uid = '') => subscribeHome(deps, session, uid, 'garden'));
2095
- ctx.command('订阅家园灵感 [uid:string]', '订阅指定 UID 的家园精灵灵感完成提醒')
2479
+ .action(async ({ session }, args = '') => {
2480
+ const parsed = parseHomeSubscribeArgs(args);
2481
+ return subscribeHome(deps, session, parsed.uid, 'inspiration', parsed.mentionAll);
2482
+ });
2483
+ ctx.command('订阅家园生蛋 [args:text]', '订阅指定 UID 的家园精灵生蛋提醒')
2096
2484
  .userFields(['authority'])
2097
- .action(async ({ session }, uid = '') => subscribeHome(deps, session, uid, 'inspiration'));
2098
- ctx.command('订阅家园生蛋 [uid:string]', '订阅指定 UID 的家园精灵生蛋提醒')
2485
+ .action(async ({ session }, args = '') => {
2486
+ const parsed = parseHomeSubscribeArgs(args);
2487
+ return subscribeHome(deps, session, parsed.uid, 'egg', parsed.mentionAll);
2488
+ });
2489
+ // 查看当前会话的家园订阅列表。
2490
+ ctx.command('我的家园订阅', '查看当前会话的家园订阅列表')
2099
2491
  .userFields(['authority'])
2100
- .action(async ({ session }, uid = '') => subscribeHome(deps, session, uid, 'egg'));
2492
+ .action(async ({ session }) => {
2493
+ const target = sessionTarget(session);
2494
+ const kindLabel = { garden: '菜园', inspiration: '灵感', egg: '生蛋' };
2495
+ const subs = Object.values(deps.homeSubMgr.getAll()).filter(sub => sub.platform === target.platform
2496
+ && (sub.channel_id || '') === (target.channelId || '')
2497
+ && (sub.user_id || '') === (target.userId || ''));
2498
+ if (!subs.length)
2499
+ return (0, permissions_1.isDirectSession)(session) ? '你当前没有家园订阅。' : '本群当前没有家园订阅。';
2500
+ const formatTime = (ts) => (ts ? new Date(ts * 1000).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }) : '从未');
2501
+ const lines = [`当前家园订阅(共 ${subs.length} 个):`, ''];
2502
+ for (const sub of subs) {
2503
+ const mentionText = sub.mention_all && !sub.user_id ? ',命中后 @全体' : '';
2504
+ lines.push(`UID ${sub.uid}(${kindLabel[sub.kind] || sub.kind}${mentionText}):上次检查 ${formatTime(sub.last_check_time)},上次推送 ${formatTime(sub.last_push_time)}`);
2505
+ }
2506
+ lines.push('');
2507
+ lines.push('发送 取消订阅家园 [菜园/灵感/生蛋] [UID] 取消订阅');
2508
+ return lines.join('\n');
2509
+ });
2101
2510
  ctx.command('取消订阅家园 [kind:string] [uid:string]', '取消当前会话的家园订阅')
2102
2511
  .userFields(['authority'])
2103
2512
  .action(async ({ session }, kind = '全部', uid = '') => {