koishi-plugin-rocom 1.0.17 → 1.0.19

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 (57) hide show
  1. package/lib/announcement-service.d.ts +21 -0
  2. package/lib/announcement-service.js +63 -0
  3. package/lib/async-control.d.ts +3 -0
  4. package/lib/async-control.js +48 -0
  5. package/lib/client.d.ts +49 -9
  6. package/lib/client.js +250 -141
  7. package/lib/commands/egg.js +101 -9
  8. package/lib/commands/merchant.js +144 -146
  9. package/lib/commands/query.js +148 -117
  10. package/lib/commands/ranking.d.ts +2 -0
  11. package/lib/commands/ranking.js +31 -0
  12. package/lib/commands/share-code.d.ts +2 -0
  13. package/lib/commands/share-code.js +66 -0
  14. package/lib/commands/tools.js +47 -59
  15. package/lib/egg-service.d.ts +125 -20
  16. package/lib/egg-service.js +364 -96
  17. package/lib/img//345/274/202/350/211/262.png +0 -0
  18. package/lib/img//347/202/253/345/275/251.png +0 -0
  19. package/lib/index.d.ts +5 -0
  20. package/lib/index.js +193 -8740
  21. package/lib/merchant-clock.d.ts +22 -0
  22. package/lib/merchant-clock.js +95 -0
  23. package/lib/merchant-service.d.ts +24 -0
  24. package/lib/merchant-service.js +148 -0
  25. package/lib/permissions.d.ts +16 -0
  26. package/lib/permissions.js +66 -0
  27. package/lib/pet-data-service.js +7 -9
  28. package/lib/pet-size.d.ts +13 -0
  29. package/lib/pet-size.js +54 -0
  30. package/lib/player-service.d.ts +37 -0
  31. package/lib/player-service.js +201 -0
  32. package/lib/ranking-service.d.ts +46 -0
  33. package/lib/ranking-service.js +137 -0
  34. package/lib/render-templates/announcement/detail.html +8 -0
  35. package/lib/render-templates/announcement/list.html +5 -0
  36. package/lib/render-templates/announcement/style.css +1 -0
  37. package/lib/render-templates/pet-ranking/index.html +99 -0
  38. package/lib/render-templates/pet-ranking/style.css +338 -0
  39. package/lib/render-templates/player-search/index.html +7 -0
  40. package/lib/render-templates/searcheggs/index.html +1 -1
  41. package/lib/render-templates/searcheggs/size.html +70 -68
  42. package/lib/render-templates/share-code-team/index.html +105 -0
  43. package/lib/render-templates/share-code-team/style.css +345 -0
  44. package/lib/render.d.ts +15 -2
  45. package/lib/render.js +120 -37
  46. package/lib/send-image.d.ts +1 -1
  47. package/lib/send-image.js +12 -1
  48. package/lib/share-code-service.d.ts +32 -0
  49. package/lib/share-code-service.js +184 -0
  50. package/lib/subscription-runner.d.ts +6 -0
  51. package/lib/subscription-runner.js +21 -0
  52. package/lib/subscription-send.d.ts +4 -2
  53. package/lib/subscription-send.js +45 -20
  54. package/lib/types.d.ts +5 -0
  55. package/lib/user.d.ts +5 -0
  56. package/package.json +4 -1
  57. package/readme.md +145 -124
@@ -0,0 +1,22 @@
1
+ export interface ClockParts {
2
+ date: string;
3
+ hour: number;
4
+ minute: number;
5
+ second: number;
6
+ }
7
+ export declare class MerchantClock {
8
+ private formatter;
9
+ private fixedOffset;
10
+ readonly zone: string;
11
+ constructor(requested?: string);
12
+ parts(nowMs?: number): ClockParts;
13
+ date(nowMs?: number): string;
14
+ /** Boundaries in absolute time, including 23/25-hour DST days. */
15
+ dayBounds(nowMs?: number): {
16
+ start: number;
17
+ end: number;
18
+ };
19
+ minuteOfDay(nowMs?: number): number;
20
+ timeText(nowMs?: number): string;
21
+ nextTimeFor(times: string[], nowMs?: number): number | null;
22
+ }
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MerchantClock = void 0;
4
+ function pad(value, width = 2) {
5
+ return String(value).padStart(width, '0');
6
+ }
7
+ // 统一时钟:默认上海时区失败时固定 +08:00,避免 UTC 服务器上商品日期/轮次错位。
8
+ class MerchantClock {
9
+ formatter;
10
+ fixedOffset = false;
11
+ zone;
12
+ constructor(requested = 'Asia/Shanghai') {
13
+ const wanted = requested || 'Asia/Shanghai';
14
+ let zone = wanted;
15
+ try {
16
+ new Intl.DateTimeFormat('en-CA', { timeZone: zone }).format(0);
17
+ }
18
+ catch {
19
+ if (wanted === 'Asia/Shanghai') {
20
+ zone = 'UTC';
21
+ this.fixedOffset = true;
22
+ }
23
+ else {
24
+ zone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
25
+ }
26
+ }
27
+ this.zone = this.fixedOffset ? 'UTC+08:00' : zone;
28
+ this.formatter = new Intl.DateTimeFormat('en-CA', {
29
+ timeZone: zone,
30
+ hourCycle: 'h23',
31
+ year: 'numeric',
32
+ month: '2-digit',
33
+ day: '2-digit',
34
+ hour: '2-digit',
35
+ minute: '2-digit',
36
+ second: '2-digit',
37
+ });
38
+ }
39
+ parts(nowMs = Date.now()) {
40
+ const timestamp = nowMs + (this.fixedOffset ? 8 * 3600_000 : 0);
41
+ const values = {};
42
+ for (const part of this.formatter.formatToParts(timestamp)) {
43
+ if (part.type !== 'literal')
44
+ values[part.type] = part.value;
45
+ }
46
+ return {
47
+ date: `${values.year}-${values.month}-${values.day}`,
48
+ hour: Number(values.hour),
49
+ minute: Number(values.minute),
50
+ second: Number(values.second),
51
+ };
52
+ }
53
+ date(nowMs = Date.now()) {
54
+ return this.parts(nowMs).date;
55
+ }
56
+ /** Boundaries in absolute time, including 23/25-hour DST days. */
57
+ dayBounds(nowMs = Date.now()) {
58
+ const day = this.date(nowMs);
59
+ const edge = (before) => {
60
+ let low = before ? nowMs - 48 * 3600_000 : nowMs;
61
+ let high = before ? nowMs : nowMs + 48 * 3600_000;
62
+ while (high - low > 1) {
63
+ const mid = Math.floor((low + high) / 2);
64
+ if (before ? this.date(mid) < day : this.date(mid) === day)
65
+ low = mid;
66
+ else
67
+ high = mid;
68
+ }
69
+ return high;
70
+ };
71
+ return { start: edge(true), end: edge(false) };
72
+ }
73
+ minuteOfDay(nowMs = Date.now()) {
74
+ const p = this.parts(nowMs);
75
+ return p.hour * 60 + p.minute;
76
+ }
77
+ timeText(nowMs = Date.now()) {
78
+ const p = this.parts(nowMs);
79
+ return `${pad(p.hour)}:${pad(p.minute)}`;
80
+ }
81
+ // 下一个匹配给定 HH:MM 墙钟列表的时刻(毫秒),扫描精度 1 分钟。
82
+ nextTimeFor(times, nowMs = Date.now()) {
83
+ const targets = new Set(times.map(item => String(item || '').trim()).filter(Boolean));
84
+ if (!targets.size)
85
+ return null;
86
+ let cursor = Math.floor(nowMs / 60000) * 60000 + 60000;
87
+ for (let i = 0; i < 60 * 24 * 8; i++) {
88
+ if (targets.has(this.timeText(cursor)))
89
+ return cursor;
90
+ cursor += 60000;
91
+ }
92
+ return null;
93
+ }
94
+ }
95
+ exports.MerchantClock = MerchantClock;
@@ -0,0 +1,24 @@
1
+ export interface MerchantItem {
2
+ id: string;
3
+ parentId?: string;
4
+ name: string;
5
+ icon: string;
6
+ price: number | null;
7
+ limit: number | null;
8
+ startsAt: number | null;
9
+ endsAt: number | null;
10
+ active: boolean;
11
+ category: string;
12
+ source: 'live' | 'legacy';
13
+ }
14
+ export interface NormalizedMerchant {
15
+ source: 'live' | 'legacy';
16
+ items: MerchantItem[];
17
+ }
18
+ export declare const numberOrNull: (v: unknown) => number | null;
19
+ export declare function livePrice(value: any): number | null;
20
+ 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[];
23
+ export declare function isLiveMerchantPayload(payload: any): boolean;
24
+ export declare function normalizeMerchant(payload: any, nowMs?: number): NormalizedMerchant;
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.numberOrNull = void 0;
4
+ exports.livePrice = livePrice;
5
+ exports.merchantTimestampMs = merchantTimestampMs;
6
+ exports.normalizeLiveMerchant = normalizeLiveMerchant;
7
+ exports.normalizeLegacyMerchant = normalizeLegacyMerchant;
8
+ exports.isLiveMerchantPayload = isLiveMerchantPayload;
9
+ exports.normalizeMerchant = normalizeMerchant;
10
+ const client_1 = require("./client");
11
+ const numberOrNull = (v) => {
12
+ if (v === undefined || v === null || v === '')
13
+ return null;
14
+ const n = Number(v);
15
+ return Number.isFinite(n) ? n : null;
16
+ };
17
+ exports.numberOrNull = numberOrNull;
18
+ // 价格可能直接是数字,也可能是 { real, origin } 或 { amount }(上游 v4.1.0)。
19
+ function livePrice(value) {
20
+ const selected = value && typeof value === 'object'
21
+ ? value.real ?? value.origin : value;
22
+ return (0, exports.numberOrNull)(selected && typeof selected === 'object'
23
+ ? selected.amount : selected);
24
+ }
25
+ // 秒/毫秒时间戳统一为毫秒;0/空值视为无时间。
26
+ function merchantTimestampMs(value) {
27
+ if (value === null || value === undefined || value === '' || value === 0 || value === '0')
28
+ return null;
29
+ const timestamp = Number(value);
30
+ if (!Number.isFinite(timestamp))
31
+ return null;
32
+ return Math.abs(timestamp) < 100_000_000_000 ? timestamp * 1000 : timestamp;
33
+ }
34
+ function mappingById(payload) {
35
+ const mapping = payload?._goods_mapping ?? payload?.goods_mapping;
36
+ const list = Array.isArray(mapping) ? mapping : [];
37
+ const map = new Map();
38
+ for (const item of list) {
39
+ if ((0, client_1.isObject)(item) && item.goods_id !== undefined && item.goods_id !== null) {
40
+ map.set(String(item.goods_id), item);
41
+ }
42
+ }
43
+ return map;
44
+ }
45
+ function liveGoodsName(meta, item, goodsId) {
46
+ const name = String(meta?.goods_name || meta?.name || item?.name || item?.goods_name || '').trim();
47
+ if (name)
48
+ return name;
49
+ return goodsId !== undefined && goodsId !== null && goodsId !== '' ? `商品 ${goodsId}` : '未知商品';
50
+ }
51
+ function liveGoodsIcon(meta, item) {
52
+ return String(item?.icon_url || item?.iconUrl || meta?.icon_url || meta?.icon || '').trim();
53
+ }
54
+ // 实时商人 goods 结构:按 mapping 补齐名称/图标,递归 sub_goods,按“商品 ID + 时间窗口 + 父商品”去重。
55
+ function normalizeLiveMerchant(payload, nowMs = Date.now()) {
56
+ const map = mappingById(payload);
57
+ const items = [];
58
+ const seen = new Set();
59
+ const add = (raw, parentId) => {
60
+ if (!(0, client_1.isObject)(raw))
61
+ return;
62
+ const goodsId = raw.goods_id ?? raw.id;
63
+ const meta = map.get(String(goodsId)) || {};
64
+ const startsAt = merchantTimestampMs(raw.start_time ?? payload?.start_time);
65
+ const endsAt = merchantTimestampMs(raw.disable_time ?? raw.end_time);
66
+ const key = `${String(goodsId)}|${startsAt ?? ''}|${endsAt ?? ''}|${parentId ?? ''}`;
67
+ if (seen.has(key))
68
+ return;
69
+ seen.add(key);
70
+ items.push({
71
+ id: String(goodsId ?? ''),
72
+ parentId,
73
+ name: liveGoodsName(meta, raw, goodsId),
74
+ icon: liveGoodsIcon(meta, raw),
75
+ price: livePrice(raw.price) ?? (0, exports.numberOrNull)(meta.price),
76
+ limit: (0, exports.numberOrNull)(raw.limit_buy_num ?? raw.buy_limit_num ?? meta.buy_limit_num),
77
+ startsAt,
78
+ endsAt,
79
+ active: (startsAt === null || nowMs >= startsAt) && (endsAt === null || nowMs < endsAt),
80
+ category: parentId ? '子商品' : '商品',
81
+ source: 'live',
82
+ });
83
+ const children = Array.isArray(raw.sub_goods) ? raw.sub_goods : [];
84
+ for (const child of children)
85
+ add(child, String(goodsId ?? ''));
86
+ };
87
+ const goods = Array.isArray(payload?.goods) ? payload.goods : [];
88
+ for (const raw of goods)
89
+ add(raw);
90
+ return items;
91
+ }
92
+ // 旧接口:merchantActivities 下 products/get_props/get_extra_props/get_pets 合并,random_goods 补价格/限购。
93
+ function normalizeLegacyMerchant(payload, nowMs = Date.now()) {
94
+ const activities = payload?.merchantActivities || payload?.merchant_activities || [];
95
+ const activity = activities[0] || {};
96
+ const randomGoods = Array.isArray(payload?.random_goods) ? payload.random_goods : [];
97
+ const metaByName = new Map();
98
+ for (const item of randomGoods) {
99
+ const name = String(item?.goods_name || item?.name || '').trim();
100
+ if (name)
101
+ metaByName.set(name, item);
102
+ }
103
+ const buckets = [
104
+ ['道具', activity.get_props || []],
105
+ ['额外道具', activity.get_extra_props || []],
106
+ ['精灵', activity.get_pets || []],
107
+ ['商品', activity.products || []],
108
+ ['商品', activity.product_list || []],
109
+ ];
110
+ const items = [];
111
+ const seen = new Set();
112
+ for (const [category, list] of buckets) {
113
+ for (const raw of Array.isArray(list) ? list : []) {
114
+ if (!(0, client_1.isObject)(raw))
115
+ continue;
116
+ const name = String(raw.name || raw.goods_name || '未知商品').trim() || '未知商品';
117
+ const startsAt = merchantTimestampMs(raw.start_time) ?? merchantTimestampMs(activity.start_time);
118
+ const endsAt = merchantTimestampMs(raw.end_time) ?? merchantTimestampMs(activity.end_time);
119
+ const key = `${raw.id ?? ''}|${name}|${startsAt ?? 0}|${endsAt ?? 'inf'}`;
120
+ if (seen.has(key))
121
+ continue;
122
+ seen.add(key);
123
+ const meta = metaByName.get(name) || {};
124
+ items.push({
125
+ id: String(raw.id ?? ''),
126
+ name,
127
+ icon: String(raw.icon_url || raw.iconUrl || meta.icon_url || meta.icon || '').trim(),
128
+ price: (0, exports.numberOrNull)(raw.price) ?? (0, exports.numberOrNull)(meta.price),
129
+ limit: (0, exports.numberOrNull)(raw.buy_limit_num ?? raw.limit_buy_num) ?? (0, exports.numberOrNull)(meta.buy_limit_num),
130
+ startsAt,
131
+ endsAt,
132
+ active: (startsAt === null || nowMs >= startsAt) && (endsAt === null || nowMs < endsAt),
133
+ category,
134
+ source: 'legacy',
135
+ });
136
+ }
137
+ }
138
+ return items;
139
+ }
140
+ function isLiveMerchantPayload(payload) {
141
+ return (0, client_1.isObject)(payload) && Array.isArray(payload.goods);
142
+ }
143
+ function normalizeMerchant(payload, nowMs = Date.now()) {
144
+ if (isLiveMerchantPayload(payload)) {
145
+ return { source: 'live', items: normalizeLiveMerchant(payload, nowMs) };
146
+ }
147
+ return { source: 'legacy', items: normalizeLegacyMerchant(payload, nowMs) };
148
+ }
@@ -0,0 +1,16 @@
1
+ import type { PluginConfig } from './types';
2
+ export type GroupRole = 'owner' | 'admin' | 'member' | 'unknown';
3
+ export interface SubscriptionPermissionInput {
4
+ privateChat: boolean;
5
+ privateAllowed: boolean;
6
+ groupRole: GroupRole;
7
+ groupAdminEnabled: boolean;
8
+ botAdminEnabled: boolean;
9
+ whitelisted: boolean;
10
+ authority: number;
11
+ botAdminAuthority: number;
12
+ }
13
+ export declare function mayManageSubscription(p: SubscriptionPermissionInput): boolean;
14
+ export declare function groupRoleFromSession(session: any): GroupRole;
15
+ export declare function isDirectSession(session: any): boolean;
16
+ export declare function canManageSubscription(config: PluginConfig, session: any, privateAllowed?: boolean): boolean;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mayManageSubscription = mayManageSubscription;
4
+ exports.groupRoleFromSession = groupRoleFromSession;
5
+ exports.isDirectSession = isDirectSession;
6
+ exports.canManageSubscription = canManageSubscription;
7
+ // 统一订阅权限策略:群主/管理员与 Bot 管理员按独立开关生效。
8
+ function mayManageSubscription(p) {
9
+ if (p.privateChat)
10
+ return p.privateAllowed;
11
+ if (p.botAdminEnabled && (p.whitelisted || p.authority >= p.botAdminAuthority))
12
+ return true;
13
+ return p.groupAdminEnabled && (p.groupRole === 'owner' || p.groupRole === 'admin');
14
+ }
15
+ // 从平台会话中读取可信角色;只接受适配器规范化的字符串角色。
16
+ function groupRoleFromSession(session) {
17
+ const collect = (value, output) => {
18
+ if (value === undefined || value === null)
19
+ return;
20
+ if (Array.isArray(value)) {
21
+ for (const item of value)
22
+ collect(item, output);
23
+ return;
24
+ }
25
+ output.push(String(value));
26
+ };
27
+ const roles = [];
28
+ const member = session?.event?.member || session?.member;
29
+ collect(member?.roles, roles);
30
+ collect(member?.role, roles);
31
+ const sender = session?.event?.sender || session?.sender;
32
+ collect(sender?.role, roles);
33
+ const author = session?.event?.author || session?.author;
34
+ collect(author?.member_role, roles);
35
+ collect(author?.memberRole, roles);
36
+ collect(member?.member_role, roles);
37
+ // OneBot 原始事件由适配器提供;不从消息正文读取角色。
38
+ collect(session?.onebot?.sender?.role, roles);
39
+ const normalized = roles.map(role => role.trim().toLowerCase());
40
+ if (normalized.some(role => ['owner', '群主', '创建者'].includes(role)))
41
+ return 'owner';
42
+ if (normalized.some(role => ['admin', 'administrator', '管理员'].includes(role)))
43
+ return 'admin';
44
+ if (normalized.some(role => ['member', '普通成员'].includes(role)))
45
+ return 'member';
46
+ return 'unknown';
47
+ }
48
+ function isDirectSession(session) {
49
+ if (typeof session?.isDirect === 'boolean')
50
+ return session.isDirect;
51
+ const type = session?.event?.channel?.type;
52
+ if (type !== undefined)
53
+ return type === 1 || type === 'DIRECT';
54
+ return !session?.guildId;
55
+ }
56
+ function canManageSubscription(config, session, privateAllowed = true) {
57
+ return mayManageSubscription({
58
+ privateChat: isDirectSession(session), privateAllowed,
59
+ groupRole: groupRoleFromSession(session),
60
+ groupAdminEnabled: config.subscriptionGroupAdminEnabled !== false,
61
+ botAdminEnabled: config.subscriptionBotAdminEnabled !== false,
62
+ whitelisted: config.adminUserIds.includes(session?.userId || ''),
63
+ authority: Number(session?.user?.authority ?? 0) || 0,
64
+ botAdminAuthority: Math.max(1, Number(config.subscriptionBotAdminAuthority) || 4),
65
+ });
66
+ }
@@ -24,6 +24,7 @@ exports.petDataSkills = petDataSkills;
24
24
  exports.petDataCardItems = petDataCardItems;
25
25
  exports.petDataExtractItems = petDataExtractItems;
26
26
  exports.buildPetDataRenderData = buildPetDataRenderData;
27
+ const pet_size_1 = require("./pet-size");
27
28
  const CN_TIME_ZONE = 'Asia/Shanghai';
28
29
  function petDataDisplay(value, defaultText = '--') {
29
30
  if (value === undefined || value === null || value === '')
@@ -105,12 +106,14 @@ function petDataKgCompact(grams) {
105
106
  const text = (number / 1000).toFixed(2).replace(/0+$/, '').replace(/\.$/, '');
106
107
  return `${text}kg`;
107
108
  }
108
- // 上游 v3.7.0:接入 Wiki 精灵体重范围,前 5% 标小块头、后 5% 标大块头。
109
+ // 上游 v3.7.0:接入 Wiki 精灵体重范围,与查蛋共用同一判定。
109
110
  function petDataWeightSizeInfo(pet, wikiPet) {
110
111
  const bodySize = wikiPet && typeof wikiPet === 'object' ? wikiPet.body_size : null;
111
112
  const weightRange = bodySize && typeof bodySize === 'object' ? bodySize.weight : null;
112
113
  if (!weightRange || typeof weightRange !== 'object')
113
114
  return {};
115
+ if ([pet?.weight, weightRange.min_g, weightRange.max_g].some(value => value == null || value === ''))
116
+ return {};
114
117
  const current = Number(pet?.weight);
115
118
  const low = Number(weightRange.min_g);
116
119
  const high = Number(weightRange.max_g);
@@ -118,15 +121,10 @@ function petDataWeightSizeInfo(pet, wikiPet) {
118
121
  return {};
119
122
  if (high <= low)
120
123
  return {};
121
- const span = high - low;
122
- const smallCut = low + span * 0.05;
123
- const largeCut = high - span * 0.05;
124
+ const size = (0, pet_size_1.classifyWeight)(current / 1000, low / 1000, high / 1000);
124
125
  const rangeText = `${petDataKgCompact(low)}-${petDataKgCompact(high)}`;
125
- if (current <= smallCut) {
126
- return { label: '小块头', className: 'size-small', hint: `小块头 · ≤${petDataKgCompact(smallCut)} · 范围 ${rangeText}` };
127
- }
128
- if (current >= largeCut) {
129
- return { label: '大块头', className: 'size-large', hint: `大块头 · ≥${petDataKgCompact(largeCut)} · 范围 ${rangeText}` };
126
+ if (size) {
127
+ return { label: size.label, className: size.css, hint: `${size.label} · ${size.css === 'size-small' ? '≤' : '≥'}${petDataKgCompact(size.threshold * 1000)} · 范围 ${rangeText}` };
130
128
  }
131
129
  return { label: '', className: '', hint: `范围 ${rangeText}` };
132
130
  }
@@ -0,0 +1,13 @@
1
+ export interface WeightClass {
2
+ label: '小块头' | '大块头';
3
+ css: 'size-small' | 'size-large';
4
+ threshold: number;
5
+ }
6
+ export declare function classifyWeight(weightKg: number, minKg: number, maxKg: number): WeightClass | null;
7
+ export interface SizeVariant {
8
+ size_variant: string;
9
+ size_variant_label: string;
10
+ size_variant_class: string;
11
+ size_variant_hint: string;
12
+ }
13
+ export declare function sizeVariantPayload(queryWeight: unknown, minKg: unknown, maxKg: unknown, rangeText?: string): SizeVariant;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.classifyWeight = classifyWeight;
4
+ exports.sizeVariantPayload = sizeVariantPayload;
5
+ // 体重范围下端 5% / 上端 5% 判定大小块头;使用未四舍五入的数值比较。
6
+ function classifyWeight(weightKg, minKg, maxKg) {
7
+ if (![weightKg, minKg, maxKg].every(Number.isFinite) || maxKg <= minKg)
8
+ return null;
9
+ if (weightKg < minKg || weightKg > maxKg)
10
+ return null;
11
+ const span = maxKg - minKg;
12
+ const small = minKg + span * 0.05;
13
+ const large = maxKg - span * 0.05;
14
+ if (weightKg <= small)
15
+ return { label: '小块头', css: 'size-small', threshold: small };
16
+ if (weightKg >= large)
17
+ return { label: '大块头', css: 'size-large', threshold: large };
18
+ return null;
19
+ }
20
+ function num(value) {
21
+ if (value === null || value === undefined || value === '')
22
+ return null;
23
+ const parsed = Number(value);
24
+ return Number.isFinite(parsed) ? parsed : null;
25
+ }
26
+ function formatNumber(value, digits = 3) {
27
+ return String(Number(value.toFixed(digits)));
28
+ }
29
+ const EMPTY_VARIANT = {
30
+ size_variant: '',
31
+ size_variant_label: '',
32
+ size_variant_class: '',
33
+ size_variant_hint: '',
34
+ };
35
+ function sizeVariantPayload(queryWeight, minKg, maxKg, rangeText = '') {
36
+ const query = num(queryWeight);
37
+ const low = num(minKg);
38
+ const high = num(maxKg);
39
+ if (query === null || low === null || high === null || high <= low)
40
+ return EMPTY_VARIANT;
41
+ const cls = classifyWeight(query, low, high);
42
+ if (!cls)
43
+ return EMPTY_VARIANT;
44
+ const limit = formatNumber(cls.threshold);
45
+ const range = rangeText || `${formatNumber(low)}-${formatNumber(high)}kg`;
46
+ return {
47
+ size_variant: cls.css === 'size-small' ? 'small' : 'large',
48
+ size_variant_label: cls.label,
49
+ size_variant_class: cls.css,
50
+ size_variant_hint: cls.label === '小块头'
51
+ ? `小块头区间:≤ ${limit} kg(体重范围 ${range} 的前 5%)`
52
+ : `大块头区间:≥ ${limit} kg(体重范围 ${range} 的后 5%)`,
53
+ };
54
+ }
@@ -0,0 +1,37 @@
1
+ export interface PlayerRow {
2
+ field?: string;
3
+ label?: string;
4
+ value?: unknown;
5
+ }
6
+ export type IngamePlayerPayload = {
7
+ rows?: PlayerRow[];
8
+ notes?: unknown[];
9
+ title?: string;
10
+ player_info?: Record<string, any>;
11
+ player_card_brief_info?: Record<string, any>;
12
+ [key: string]: any;
13
+ };
14
+ export declare function resourceUrl(value: unknown, apiBaseUrl: string): string;
15
+ export declare function playerRows(payload: any, uid: string): PlayerRow[];
16
+ /** 玩家时间统一按北京时间展示,不依赖 Bot 所在服务器的时区。 */
17
+ export declare function formatPlayerTime(value: unknown): string;
18
+ export declare function cleanPlayerFieldValue(field: string, value: unknown): string;
19
+ export interface ParsedPlayer {
20
+ title: string;
21
+ nickname: string;
22
+ uid: string;
23
+ level: string;
24
+ signature: string;
25
+ rowMap: Record<string, string>;
26
+ labelMap: Record<string, string>;
27
+ }
28
+ export declare function parseIngamePlayerPayload(payload: IngamePlayerPayload | null | undefined, uid: string): ParsedPlayer;
29
+ export declare function playerField(parsed: ParsedPlayer | null, field: string, defaultValue?: string): string;
30
+ export interface PlayerView {
31
+ parsed: ParsedPlayer;
32
+ cardImageUrl: string;
33
+ }
34
+ export declare function buildPlayerView(searchPayload: any, cardPayload: any, uid: string, apiBaseUrl: string): PlayerView;
35
+ export declare function mergePlayerPayloads(searchPayload: any, cardPayload: any): any;
36
+ export declare function playerPayloadUid(payload: any): string;
37
+ export declare function buildPlayerText(payload: any, uid: string): string;