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
@@ -0,0 +1,47 @@
1
+ export declare const MERCHANT_ALL_ALIASES: Set<string>;
2
+ export interface MerchantCatalogItem {
3
+ index: number;
4
+ name: string;
5
+ goods_id: number;
6
+ item_id: number;
7
+ item_num: number;
8
+ }
9
+ export declare function isAllAlias(token?: string): boolean;
10
+ /**
11
+ * 把后端商品池归一成带序号的目录。
12
+ *
13
+ * 序号只取后端返回的顺序(index = 位置 + 1),解析订阅时立刻换成商品名落盘,
14
+ * 所以后端调整目录顺序不会影响已有订阅。
15
+ */
16
+ export declare function buildMerchantCatalog(randomGoods: unknown): MerchantCatalogItem[];
17
+ /**
18
+ * 解析用户输入:序号 / 商品名 / 全部。
19
+ *
20
+ * - 纯数字按序号查目录,命中后换成规范名
21
+ * - 目录里没有的文字按原样保留(后端随时会加新商品),同时记进 unrecognizedNames 供提示
22
+ * - 目录为空(离线)时不报未识别,避免误伤
23
+ */
24
+ export declare function resolveMerchantSelection(input: unknown, catalog: MerchantCatalogItem[]): {
25
+ all: boolean;
26
+ names: string[];
27
+ unknownIndexes: string[];
28
+ unrecognizedNames: string[];
29
+ };
30
+ /** 把订阅的商品名回显成「序号.名称」,方便用户对照商品列表。 */
31
+ export declare function describeMerchantItems(names: unknown, catalog: MerchantCatalogItem[]): string;
32
+ export declare class MerchantCatalogService {
33
+ private filePath;
34
+ private fetchRandomGoods;
35
+ private memory;
36
+ constructor(filePath: string, fetchRandomGoods: () => Promise<any>);
37
+ /** 同步读磁盘缓存,供命令在离线时兜底。 */
38
+ loadCachedCatalog(): MerchantCatalogItem[];
39
+ writeCache(items: MerchantCatalogItem[]): boolean;
40
+ /**
41
+ * 取商品目录:内存缓存 → 后端全量商品池 → 磁盘缓存。
42
+ * 全链路失败时返回上一次的目录(可能为空),由调用方决定怎么提示。
43
+ */
44
+ getCatalog(options?: {
45
+ refresh?: boolean;
46
+ }): Promise<MerchantCatalogItem[]>;
47
+ }
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MerchantCatalogService = exports.MERCHANT_ALL_ALIASES = void 0;
7
+ exports.isAllAlias = isAllAlias;
8
+ exports.buildMerchantCatalog = buildMerchantCatalog;
9
+ exports.resolveMerchantSelection = resolveMerchantSelection;
10
+ exports.describeMerchantItems = describeMerchantItems;
11
+ const koishi_1 = require("koishi");
12
+ const node_fs_1 = __importDefault(require("node:fs"));
13
+ const node_path_1 = __importDefault(require("node:path"));
14
+ const logger = new koishi_1.Logger('rocom-merchant');
15
+ // 把后端商品池(merchant/info?random_goods=all)归一成带序号的目录,
16
+ // 供「商人商品」列表展示与「订阅远行商人 1 3 5」按序号订阅使用。
17
+ // 商品池变化很慢,内存缓存 6 小时,避免订阅轮询反复拉全量。
18
+ const CATALOG_TTL_MS = 6 * 60 * 60 * 1000;
19
+ // 「全物品订阅」的写法。
20
+ exports.MERCHANT_ALL_ALIASES = new Set(['全部', '全物品', '全部物品', '所有', '所有物品', 'all', '*']);
21
+ function trimText(value) {
22
+ return String(value ?? '').trim();
23
+ }
24
+ function toWholeNumber(value) {
25
+ const num = Number(value);
26
+ return Number.isFinite(num) && num > 0 ? Math.floor(num) : 0;
27
+ }
28
+ function isAllAlias(token = '') {
29
+ return exports.MERCHANT_ALL_ALIASES.has(trimText(token).toLowerCase());
30
+ }
31
+ /**
32
+ * 把后端商品池归一成带序号的目录。
33
+ *
34
+ * 序号只取后端返回的顺序(index = 位置 + 1),解析订阅时立刻换成商品名落盘,
35
+ * 所以后端调整目录顺序不会影响已有订阅。
36
+ */
37
+ function buildMerchantCatalog(randomGoods) {
38
+ const items = [];
39
+ const seen = new Set();
40
+ for (const raw of Array.isArray(randomGoods) ? randomGoods : []) {
41
+ const name = trimText(raw?.goods_name);
42
+ if (!name || seen.has(name))
43
+ continue;
44
+ seen.add(name);
45
+ items.push({
46
+ index: items.length + 1,
47
+ name,
48
+ goods_id: toWholeNumber(raw?.id),
49
+ item_id: toWholeNumber(raw?.item_id),
50
+ item_num: toWholeNumber(raw?.item_num) || 1,
51
+ });
52
+ }
53
+ return items;
54
+ }
55
+ /**
56
+ * 解析用户输入:序号 / 商品名 / 全部。
57
+ *
58
+ * - 纯数字按序号查目录,命中后换成规范名
59
+ * - 目录里没有的文字按原样保留(后端随时会加新商品),同时记进 unrecognizedNames 供提示
60
+ * - 目录为空(离线)时不报未识别,避免误伤
61
+ */
62
+ function resolveMerchantSelection(input, catalog) {
63
+ const list = Array.isArray(input) ? input : [input];
64
+ const items = Array.isArray(catalog) ? catalog : [];
65
+ const names = [];
66
+ const seen = new Set();
67
+ const unknownIndexes = [];
68
+ const unrecognizedNames = [];
69
+ let all = false;
70
+ const pushName = (value) => {
71
+ const text = trimText(value);
72
+ if (!text || seen.has(text))
73
+ return;
74
+ seen.add(text);
75
+ names.push(text);
76
+ };
77
+ for (const raw of list) {
78
+ const token = trimText(raw);
79
+ if (!token)
80
+ continue;
81
+ if (isAllAlias(token)) {
82
+ all = true;
83
+ continue;
84
+ }
85
+ if (/^\d+$/.test(token)) {
86
+ const hit = items.find(item => item.index === Number(token));
87
+ if (hit)
88
+ pushName(hit.name);
89
+ else
90
+ unknownIndexes.push(token);
91
+ continue;
92
+ }
93
+ const hit = items.find(item => trimText(item.name) === token);
94
+ if (hit) {
95
+ pushName(hit.name);
96
+ continue;
97
+ }
98
+ pushName(token);
99
+ if (items.length > 0)
100
+ unrecognizedNames.push(token);
101
+ }
102
+ return { all, names, unknownIndexes, unrecognizedNames };
103
+ }
104
+ /** 把订阅的商品名回显成「序号.名称」,方便用户对照商品列表。 */
105
+ function describeMerchantItems(names, catalog) {
106
+ const indexByName = new Map();
107
+ for (const item of Array.isArray(catalog) ? catalog : []) {
108
+ const name = trimText(item?.name);
109
+ if (name)
110
+ indexByName.set(name, item.index);
111
+ }
112
+ return (Array.isArray(names) ? names : [])
113
+ .map((raw) => {
114
+ const name = trimText(raw);
115
+ if (!name)
116
+ return '';
117
+ const index = indexByName.get(name) || 0;
118
+ return index > 0 ? `${index}.${name}` : name;
119
+ })
120
+ .filter(Boolean)
121
+ .join('、');
122
+ }
123
+ class MerchantCatalogService {
124
+ filePath;
125
+ fetchRandomGoods;
126
+ memory = null;
127
+ constructor(filePath, fetchRandomGoods) {
128
+ this.filePath = filePath;
129
+ this.fetchRandomGoods = fetchRandomGoods;
130
+ }
131
+ /** 同步读磁盘缓存,供命令在离线时兜底。 */
132
+ loadCachedCatalog() {
133
+ try {
134
+ const raw = JSON.parse(node_fs_1.default.readFileSync(this.filePath, 'utf8'));
135
+ const items = Array.isArray(raw?.items) ? raw.items : [];
136
+ return items.filter((item) => trimText(item?.name));
137
+ }
138
+ catch {
139
+ return [];
140
+ }
141
+ }
142
+ writeCache(items) {
143
+ try {
144
+ const dir = node_path_1.default.dirname(this.filePath);
145
+ if (!node_fs_1.default.existsSync(dir))
146
+ node_fs_1.default.mkdirSync(dir, { recursive: true });
147
+ const payload = {
148
+ updated_at: new Date().toISOString(),
149
+ source: 'merchant/info?random_goods=all',
150
+ items,
151
+ };
152
+ const tempPath = `${this.filePath}.tmp`;
153
+ node_fs_1.default.writeFileSync(tempPath, JSON.stringify(payload, null, 2), 'utf8');
154
+ node_fs_1.default.renameSync(tempPath, this.filePath);
155
+ return true;
156
+ }
157
+ catch (error) {
158
+ logger.warn(`写入远行商人商品目录失败: ${error}`);
159
+ return false;
160
+ }
161
+ }
162
+ /**
163
+ * 取商品目录:内存缓存 → 后端全量商品池 → 磁盘缓存。
164
+ * 全链路失败时返回上一次的目录(可能为空),由调用方决定怎么提示。
165
+ */
166
+ async getCatalog(options = {}) {
167
+ const now = Date.now();
168
+ const cached = this.memory;
169
+ if (!options.refresh && cached && cached.items.length > 0 && now - cached.at < CATALOG_TTL_MS) {
170
+ return JSON.parse(JSON.stringify(cached.items));
171
+ }
172
+ try {
173
+ const payload = await this.fetchRandomGoods();
174
+ const items = buildMerchantCatalog(payload?.random_goods);
175
+ if (items.length > 0) {
176
+ this.memory = { at: now, items };
177
+ this.writeCache(items);
178
+ return JSON.parse(JSON.stringify(items));
179
+ }
180
+ logger.warn('远行商人商品目录为空,沿用本地缓存');
181
+ }
182
+ catch (error) {
183
+ logger.warn(`拉取远行商人商品目录失败: ${error}`);
184
+ }
185
+ const disk = this.loadCachedCatalog();
186
+ if (disk.length > 0) {
187
+ this.memory = { at: now, items: disk };
188
+ return JSON.parse(JSON.stringify(disk));
189
+ }
190
+ return cached?.items ? JSON.parse(JSON.stringify(cached.items)) : [];
191
+ }
192
+ }
193
+ exports.MerchantCatalogService = MerchantCatalogService;
@@ -0,0 +1,63 @@
1
+ export declare function merchantPriceDateKey(date?: Date): string;
2
+ export interface MerchantPriceSnapshot {
3
+ price: number;
4
+ origin_price: number;
5
+ buy_limit_num: number;
6
+ at: string;
7
+ }
8
+ export interface MerchantPriceEntry {
9
+ goods_id: number;
10
+ name: string;
11
+ item_id: number;
12
+ item_num: number;
13
+ buy_limit_num: number;
14
+ by_round: Record<string, MerchantPriceSnapshot>;
15
+ price: number;
16
+ origin_price: number;
17
+ round: number;
18
+ }
19
+ export interface MerchantPriceLogItem {
20
+ goods_id: number;
21
+ name: string;
22
+ goods_name?: string;
23
+ item_id?: unknown;
24
+ item_num?: unknown;
25
+ buy_limit_num?: unknown;
26
+ limit_buy_num?: unknown;
27
+ price?: unknown;
28
+ origin_price?: unknown;
29
+ }
30
+ export interface MerchantPriceLookup {
31
+ goods_id: number;
32
+ name: string;
33
+ price: number;
34
+ origin_price: number;
35
+ buy_limit_num: number;
36
+ item_id: number;
37
+ item_num: number;
38
+ round: number;
39
+ }
40
+ interface PriceLogState {
41
+ date: string;
42
+ updated_at: string;
43
+ items: Record<string, MerchantPriceEntry>;
44
+ }
45
+ export declare class MerchantPriceLog {
46
+ private filePath;
47
+ private state;
48
+ constructor(filePath: string);
49
+ /** 读盘;日期不是今天就当作已清空(游戏内自然日滚动)。 */
50
+ load(now?: Date): PriceLogState;
51
+ save(now?: Date): void;
52
+ /** 记录一批实时商品(价格 <= 0 的跳过,避免把无效值写进去)。 */
53
+ record(goods: MerchantPriceLogItem[], options?: {
54
+ now?: Date;
55
+ round?: number;
56
+ }): boolean;
57
+ /** 按 goods_id(优先同名轮次)查当天记录,查不到再退回商品名。 */
58
+ lookup(goodsId: unknown, name?: string, round?: number, now?: Date): MerchantPriceLookup | null;
59
+ /** 调试 / 测试用:当前记录快照。 */
60
+ snapshot(): any;
61
+ clear(now?: Date): void;
62
+ }
63
+ export {};
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MerchantPriceLog = void 0;
7
+ exports.merchantPriceDateKey = merchantPriceDateKey;
8
+ const koishi_1 = require("koishi");
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const logger = new koishi_1.Logger('rocom-merchant');
12
+ // 实时接口只返回当前轮次的商品价格,轮次一过就拿不到了;
13
+ // 旧接口 random_goods[].price 恒为 0。把每轮观察到的实时价格按 goods_id + 轮次
14
+ // 记下来,供当天后续轮次补价。按游戏内自然日(Asia/Shanghai)分桶,跨天自动清空。
15
+ const chinaDateFormatter = new Intl.DateTimeFormat('en-CA', {
16
+ timeZone: 'Asia/Shanghai',
17
+ year: 'numeric',
18
+ month: '2-digit',
19
+ day: '2-digit',
20
+ });
21
+ function merchantPriceDateKey(date = new Date()) {
22
+ return chinaDateFormatter.format(date);
23
+ }
24
+ function toPositiveNumber(value) {
25
+ const num = Number(value);
26
+ return Number.isFinite(num) && num > 0 ? num : 0;
27
+ }
28
+ function toWholeNumber(value) {
29
+ const num = Number(value);
30
+ return Number.isFinite(num) && num > 0 ? Math.floor(num) : 0;
31
+ }
32
+ function trimText(value) {
33
+ return String(value ?? '').trim();
34
+ }
35
+ class MerchantPriceLog {
36
+ filePath;
37
+ state = null;
38
+ constructor(filePath) {
39
+ this.filePath = filePath;
40
+ }
41
+ /** 读盘;日期不是今天就当作已清空(游戏内自然日滚动)。 */
42
+ load(now = new Date()) {
43
+ const today = merchantPriceDateKey(now);
44
+ if (this.state && this.state.date === today)
45
+ return this.state;
46
+ let raw = null;
47
+ try {
48
+ if (node_fs_1.default.existsSync(this.filePath)) {
49
+ raw = JSON.parse(node_fs_1.default.readFileSync(this.filePath, 'utf8'));
50
+ }
51
+ }
52
+ catch (error) {
53
+ logger.warn(`读取远行商人价格记录失败: ${error}`);
54
+ }
55
+ if (!raw || typeof raw !== 'object' || raw.date !== today) {
56
+ this.state = { date: today, updated_at: '', items: {} };
57
+ if (raw && raw.date !== today)
58
+ this.save(now);
59
+ return this.state;
60
+ }
61
+ this.state = {
62
+ date: today,
63
+ updated_at: trimText(raw.updated_at),
64
+ items: raw.items && typeof raw.items === 'object' ? raw.items : {},
65
+ };
66
+ return this.state;
67
+ }
68
+ save(now = new Date()) {
69
+ try {
70
+ const dir = node_path_1.default.dirname(this.filePath);
71
+ if (!node_fs_1.default.existsSync(dir))
72
+ node_fs_1.default.mkdirSync(dir, { recursive: true });
73
+ const payload = {
74
+ date: this.state?.date || merchantPriceDateKey(now),
75
+ updated_at: new Date().toISOString(),
76
+ items: this.state?.items || {},
77
+ };
78
+ this.state = payload;
79
+ const tempPath = `${this.filePath}.tmp`;
80
+ node_fs_1.default.writeFileSync(tempPath, JSON.stringify(payload, null, 2), 'utf8');
81
+ node_fs_1.default.renameSync(tempPath, this.filePath);
82
+ }
83
+ catch (error) {
84
+ logger.warn(`写入远行商人价格记录失败: ${error}`);
85
+ }
86
+ }
87
+ /** 记录一批实时商品(价格 <= 0 的跳过,避免把无效值写进去)。 */
88
+ record(goods, options = {}) {
89
+ const now = options.now instanceof Date ? options.now : new Date();
90
+ const state = this.load(now);
91
+ const round = toWholeNumber(options.round);
92
+ let changed = false;
93
+ for (const item of Array.isArray(goods) ? goods : []) {
94
+ const goodsId = toWholeNumber(item?.goods_id);
95
+ const price = toPositiveNumber(item?.price);
96
+ if (!goodsId || price <= 0)
97
+ continue;
98
+ const key = String(goodsId);
99
+ const entry = state.items[key] || {
100
+ goods_id: goodsId,
101
+ name: trimText(item?.goods_name),
102
+ item_id: toWholeNumber(item?.item_id),
103
+ item_num: toWholeNumber(item?.item_num) || 1,
104
+ buy_limit_num: toWholeNumber(item?.buy_limit_num ?? item?.limit_buy_num),
105
+ by_round: {},
106
+ price: 0,
107
+ origin_price: 0,
108
+ round: 0,
109
+ };
110
+ if (!entry.name && item?.goods_name)
111
+ entry.name = trimText(item.goods_name);
112
+ if (!entry.item_id && item?.item_id)
113
+ entry.item_id = toWholeNumber(item.item_id);
114
+ if (!entry.item_num && item?.item_num)
115
+ entry.item_num = toWholeNumber(item.item_num) || 1;
116
+ const limitNum = toWholeNumber(item?.buy_limit_num ?? item?.limit_buy_num);
117
+ if (!entry.buy_limit_num && limitNum)
118
+ entry.buy_limit_num = limitNum;
119
+ const snapshot = {
120
+ price,
121
+ origin_price: toPositiveNumber(item?.origin_price) || price,
122
+ buy_limit_num: limitNum || entry.buy_limit_num || 0,
123
+ at: now.toISOString(),
124
+ };
125
+ entry.by_round[String(round)] = snapshot;
126
+ // 顶层存最近一次观察到的价格,供没有轮次命中的商品兜底。
127
+ entry.price = snapshot.price;
128
+ entry.origin_price = snapshot.origin_price;
129
+ entry.round = round || entry.round;
130
+ state.items[key] = entry;
131
+ changed = true;
132
+ }
133
+ if (changed)
134
+ this.save(now);
135
+ return changed;
136
+ }
137
+ /** 按 goods_id(优先同名轮次)查当天记录,查不到再退回商品名。 */
138
+ lookup(goodsId, name = '', round = 0, now = new Date()) {
139
+ const items = this.load(now).items || {};
140
+ const fromEntry = (entry) => {
141
+ if (!entry)
142
+ return null;
143
+ const byRound = entry.by_round || {};
144
+ const matched = byRound[String(toWholeNumber(round))];
145
+ const source = matched || { price: entry.price, origin_price: entry.origin_price, buy_limit_num: entry.buy_limit_num, at: '' };
146
+ const price = toPositiveNumber(source?.price);
147
+ if (price <= 0)
148
+ return null;
149
+ return {
150
+ goods_id: toWholeNumber(entry.goods_id),
151
+ name: trimText(entry.name),
152
+ price,
153
+ origin_price: toPositiveNumber(source?.origin_price) || price,
154
+ buy_limit_num: toWholeNumber(source?.buy_limit_num) || toWholeNumber(entry.buy_limit_num),
155
+ item_id: toWholeNumber(entry.item_id),
156
+ item_num: toWholeNumber(entry.item_num) || 1,
157
+ round: toWholeNumber(round) || toWholeNumber(entry.round),
158
+ };
159
+ };
160
+ const normalizedId = toWholeNumber(goodsId);
161
+ if (normalizedId > 0) {
162
+ const direct = fromEntry(items[String(normalizedId)]);
163
+ if (direct)
164
+ return direct;
165
+ }
166
+ const normalizedName = trimText(name);
167
+ if (!normalizedName)
168
+ return null;
169
+ for (const entry of Object.values(items)) {
170
+ if (trimText(entry?.name) === normalizedName) {
171
+ const found = fromEntry(entry);
172
+ if (found)
173
+ return found;
174
+ }
175
+ }
176
+ return null;
177
+ }
178
+ /** 调试 / 测试用:当前记录快照。 */
179
+ snapshot() {
180
+ return JSON.parse(JSON.stringify(this.load()));
181
+ }
182
+ clear(now = new Date()) {
183
+ this.state = { date: merchantPriceDateKey(now), updated_at: '', items: {} };
184
+ this.save(now);
185
+ }
186
+ }
187
+ exports.MerchantPriceLog = MerchantPriceLog;
@@ -10,6 +10,9 @@ export interface MerchantItem {
10
10
  active: boolean;
11
11
  category: string;
12
12
  source: 'live' | 'legacy';
13
+ allDay: boolean;
14
+ itemId?: string;
15
+ itemKind?: string;
13
16
  }
14
17
  export interface NormalizedMerchant {
15
18
  source: 'live' | 'legacy';
@@ -18,7 +21,18 @@ export interface NormalizedMerchant {
18
21
  export declare const numberOrNull: (v: unknown) => number | null;
19
22
  export declare function livePrice(value: any): number | null;
20
23
  export declare function merchantTimestampMs(value: any): number | null;
21
- export declare function normalizeLiveMerchant(payload: any, nowMs?: number): MerchantItem[];
22
- export declare function normalizeLegacyMerchant(payload: any, nowMs?: number): MerchantItem[];
24
+ export interface MerchantNormalizeOptions {
25
+ secondsOfDay?: (ms: number) => number;
26
+ assetBaseUrl?: string;
27
+ fallbackIcon?: string;
28
+ }
29
+ export declare function isAllDayMerchantProduct(refreshAt: number | null, startsAt: number | null, endsAt: number | null, roundValue: unknown, secondsOfDay?: (ms: number) => number): boolean;
30
+ export declare function wikiItemAssetUrl(baseUrl: string | undefined, itemId: unknown, itemKind: unknown): string;
31
+ export declare function collectAllDayProductNames(items: Array<{
32
+ name?: string;
33
+ allDay?: boolean;
34
+ }>): Set<string>;
35
+ export declare function normalizeLiveMerchant(payload: any, nowMs?: number, options?: MerchantNormalizeOptions): MerchantItem[];
36
+ export declare function normalizeLegacyMerchant(payload: any, nowMs?: number, options?: MerchantNormalizeOptions): MerchantItem[];
23
37
  export declare function isLiveMerchantPayload(payload: any): boolean;
24
- export declare function normalizeMerchant(payload: any, nowMs?: number): NormalizedMerchant;
38
+ export declare function normalizeMerchant(payload: any, nowMs?: number, options?: MerchantNormalizeOptions): NormalizedMerchant;