koishi-plugin-rocom 1.0.11 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commands/merchant.js +114 -17
- package/lib/commands/query.js +73 -21
- package/lib/index.js +162 -47
- package/lib/render-templates/yuanxing-shangren/index.html +85 -28
- package/package.json +1 -1
package/lib/commands/merchant.js
CHANGED
|
@@ -15,6 +15,49 @@ const TEXT = {
|
|
|
15
15
|
defaultSource: '\u9ed8\u8ba4',
|
|
16
16
|
customSource: '\u81ea\u5b9a\u4e49',
|
|
17
17
|
};
|
|
18
|
+
const CATEGORY_ORDER = ['normal', 'round', 'weekend'];
|
|
19
|
+
const CATEGORY_LABELS = {
|
|
20
|
+
normal: '热销商品',
|
|
21
|
+
round: '常规商品',
|
|
22
|
+
weekend: '周末限定',
|
|
23
|
+
};
|
|
24
|
+
const CHINA_TIMEZONE = 'Asia/Shanghai';
|
|
25
|
+
const chinaPartsFormatter = new Intl.DateTimeFormat('zh-CN', {
|
|
26
|
+
timeZone: CHINA_TIMEZONE,
|
|
27
|
+
year: 'numeric',
|
|
28
|
+
month: '2-digit',
|
|
29
|
+
day: '2-digit',
|
|
30
|
+
hour: '2-digit',
|
|
31
|
+
minute: '2-digit',
|
|
32
|
+
hour12: false,
|
|
33
|
+
});
|
|
34
|
+
function getChinaParts(timestampMs) {
|
|
35
|
+
const parts = {};
|
|
36
|
+
for (const item of chinaPartsFormatter.formatToParts(new Date(timestampMs))) {
|
|
37
|
+
if (item.type !== 'literal')
|
|
38
|
+
parts[item.type] = item.value;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
hour: Number(parts.hour || '0'),
|
|
42
|
+
minute: Number(parts.minute || '0'),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function classifyMerchantItem(item) {
|
|
46
|
+
const start = normalizeTimestamp(item?.start_time);
|
|
47
|
+
const end = normalizeTimestamp(item?.end_time);
|
|
48
|
+
if (!start || !end)
|
|
49
|
+
return 'normal';
|
|
50
|
+
const durationDays = (end - start) / (1000 * 60 * 60 * 24);
|
|
51
|
+
if (durationDays >= 2)
|
|
52
|
+
return 'weekend';
|
|
53
|
+
const startParts = getChinaParts(start);
|
|
54
|
+
const endParts = getChinaParts(end);
|
|
55
|
+
const startHour = startParts.hour + startParts.minute / 60;
|
|
56
|
+
const endHour = endParts.hour + endParts.minute / 60;
|
|
57
|
+
if (startHour <= 8 && endHour >= 23.5)
|
|
58
|
+
return 'normal';
|
|
59
|
+
return 'round';
|
|
60
|
+
}
|
|
18
61
|
function normalizeTimestamp(value) {
|
|
19
62
|
if (value === null || value === undefined || value === '')
|
|
20
63
|
return null;
|
|
@@ -54,13 +97,34 @@ function getMerchantActivity(res) {
|
|
|
54
97
|
}
|
|
55
98
|
function getActiveProducts(res) {
|
|
56
99
|
const activity = getMerchantActivity(res);
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
100
|
+
const groups = [];
|
|
101
|
+
if (Array.isArray(activity?.products))
|
|
102
|
+
groups.push(activity.products);
|
|
103
|
+
if (Array.isArray(activity?.product_list))
|
|
104
|
+
groups.push(activity.product_list);
|
|
105
|
+
if (Array.isArray(activity?.get_props))
|
|
106
|
+
groups.push(activity.get_props);
|
|
107
|
+
if (Array.isArray(activity?.get_extra_props))
|
|
108
|
+
groups.push(activity.get_extra_props);
|
|
109
|
+
if (Array.isArray(activity?.get_pets))
|
|
110
|
+
groups.push(activity.get_pets);
|
|
111
|
+
const merged = [];
|
|
112
|
+
const seen = new Set();
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
for (const list of groups) {
|
|
115
|
+
for (const item of list) {
|
|
116
|
+
const start = normalizeTimestamp(item?.start_time) ?? 0;
|
|
117
|
+
const end = normalizeTimestamp(item?.end_time) ?? Infinity;
|
|
118
|
+
if (now < start || now >= end)
|
|
119
|
+
continue;
|
|
120
|
+
const key = `${item?.id ?? ''}|${item?.name ?? ''}|${start}|${end === Infinity ? 'inf' : end}`;
|
|
121
|
+
if (seen.has(key))
|
|
122
|
+
continue;
|
|
123
|
+
seen.add(key);
|
|
124
|
+
merged.push(item);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return merged;
|
|
64
128
|
}
|
|
65
129
|
function getCurrentMerchantRound() {
|
|
66
130
|
const now = new Date();
|
|
@@ -132,23 +196,56 @@ function buildMerchantRenderPayload(res) {
|
|
|
132
196
|
const products = getActiveProducts(res);
|
|
133
197
|
const roundInfo = getCurrentMerchantRound();
|
|
134
198
|
const activity = getMerchantActivity(res);
|
|
199
|
+
const renderedProducts = products.map((p) => ({
|
|
200
|
+
name: p.name || TEXT.unknown,
|
|
201
|
+
image: p.icon_url || '',
|
|
202
|
+
time_label: formatProductWindow(p),
|
|
203
|
+
category: classifyMerchantItem(p),
|
|
204
|
+
}));
|
|
205
|
+
const categoryMap = {
|
|
206
|
+
normal: [],
|
|
207
|
+
round: [],
|
|
208
|
+
weekend: [],
|
|
209
|
+
};
|
|
210
|
+
for (const product of renderedProducts) {
|
|
211
|
+
categoryMap[product.category].push(product);
|
|
212
|
+
}
|
|
213
|
+
const categories = CATEGORY_ORDER
|
|
214
|
+
.filter(key => categoryMap[key].length > 0)
|
|
215
|
+
.map(key => ({
|
|
216
|
+
key,
|
|
217
|
+
label: CATEGORY_LABELS[key],
|
|
218
|
+
products: categoryMap[key],
|
|
219
|
+
}));
|
|
135
220
|
const data = {
|
|
136
221
|
background: '',
|
|
137
222
|
title: activity.name || TEXT.merchant,
|
|
138
223
|
subtitle: activity.start_date || '\u6bcf\u65e5 08:00 / 12:00 / 16:00 / 20:00 \u5237\u65b0',
|
|
139
224
|
titleIcon: true,
|
|
140
|
-
product_count:
|
|
225
|
+
product_count: renderedProducts.length,
|
|
141
226
|
round_info: roundInfo,
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
image: p.icon_url || '',
|
|
145
|
-
time_label: formatProductWindow(p),
|
|
146
|
-
})),
|
|
227
|
+
categories,
|
|
228
|
+
products: renderedProducts,
|
|
147
229
|
};
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
230
|
+
const fallbackLines = [];
|
|
231
|
+
if (renderedProducts.length) {
|
|
232
|
+
fallbackLines.push(`\u8fdc\u884c\u5546\u4eba\u5f53\u524d\u5546\u54c1\uff08\u5171 ${renderedProducts.length} \u4ef6\uff09`);
|
|
233
|
+
fallbackLines.push(`\u8f6e\u6b21\uff1a${roundInfo.current || TEXT.notOpen}\uff0c\u5269\u4f59\uff1a${roundInfo.countdown}`);
|
|
234
|
+
fallbackLines.push('');
|
|
235
|
+
for (const cat of categories) {
|
|
236
|
+
fallbackLines.push(`\u3010${cat.label}\u3011`);
|
|
237
|
+
cat.products.forEach((product, index) => {
|
|
238
|
+
const tail = product.time_label ? ` (${product.time_label})` : '';
|
|
239
|
+
fallbackLines.push(` ${index + 1}. ${product.name}${tail}`);
|
|
240
|
+
});
|
|
241
|
+
fallbackLines.push('');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
fallbackLines.push('\u5f53\u524d\u8fdc\u884c\u5546\u4eba\u6682\u65e0\u5546\u54c1\u3002');
|
|
246
|
+
}
|
|
247
|
+
const fallback = fallbackLines.join('\n').trimEnd();
|
|
248
|
+
return { products: renderedProducts, roundInfo, data, fallback };
|
|
152
249
|
}
|
|
153
250
|
async function checkMerchantSubscriptions(deps) {
|
|
154
251
|
const { ctx, client, merchantSubMgr, renderer, config } = deps;
|
package/lib/commands/query.js
CHANGED
|
@@ -258,16 +258,28 @@ function normalizeDurationSeconds(value) {
|
|
|
258
258
|
function formatHomeRemaining(targetTs, nowTs = Math.floor(Date.now() / 1000)) {
|
|
259
259
|
if (!targetTs)
|
|
260
260
|
return '未开始';
|
|
261
|
-
|
|
262
|
-
if (remain <= 0)
|
|
261
|
+
if (nowTs >= targetTs)
|
|
263
262
|
return '已完成';
|
|
264
|
-
const
|
|
263
|
+
const remain = Math.max(0, targetTs - nowTs);
|
|
264
|
+
const days = Math.floor(remain / 86400);
|
|
265
|
+
const hours = Math.floor((remain % 86400) / 3600);
|
|
265
266
|
const minutes = Math.floor((remain % 3600) / 60);
|
|
266
|
-
|
|
267
|
-
|
|
267
|
+
const seconds = remain % 60;
|
|
268
|
+
if (days > 0)
|
|
269
|
+
return hours > 0 ? `${days}天${hours}小时` : `${days}天`;
|
|
268
270
|
if (hours > 0)
|
|
269
271
|
return `${hours}小时${minutes}分钟`;
|
|
270
|
-
|
|
272
|
+
if (minutes > 0)
|
|
273
|
+
return `${minutes}分${seconds}秒`;
|
|
274
|
+
return `${seconds}秒`;
|
|
275
|
+
}
|
|
276
|
+
function formatEggRemaining(targetTs, nowTs = Math.floor(Date.now() / 1000)) {
|
|
277
|
+
if (!targetTs || nowTs >= targetTs)
|
|
278
|
+
return '0分钟';
|
|
279
|
+
const remain = Math.max(0, targetTs - nowTs);
|
|
280
|
+
const totalHours = Math.floor(remain / 3600);
|
|
281
|
+
const minutes = Math.floor((remain % 3600) / 60);
|
|
282
|
+
return `${totalHours}小时${minutes}分钟`;
|
|
271
283
|
}
|
|
272
284
|
function homeInfoPayload(res) {
|
|
273
285
|
const payload = res || {};
|
|
@@ -856,31 +868,59 @@ function extractHomePet(raw, index, guard = false) {
|
|
|
856
868
|
const petId = homePet.pet_cfg_id || homePet.pet_id || homePet.pet_base_id || raw.pet_cfg_id || raw.pet_id || raw.id;
|
|
857
869
|
if (['', '0'].includes(String(petId || '0')) && !guard)
|
|
858
870
|
return null;
|
|
859
|
-
const feedInfo = homePet.feed_info && typeof homePet.feed_info === 'object' ? homePet.feed_info : {};
|
|
860
|
-
const beginTime = normalizeEpochSeconds(feedInfo.begin_time);
|
|
861
|
-
const timeCost = normalizeDurationSeconds(feedInfo.time_cost);
|
|
862
|
-
let readyAt = normalizeEpochSeconds(homePet.pet_rip_time || raw.pet_rip_time || raw.rip_time);
|
|
863
|
-
if (!readyAt && beginTime && timeCost)
|
|
864
|
-
readyAt = beginTime + timeCost;
|
|
865
871
|
const nowTs = Math.floor(Date.now() / 1000);
|
|
866
|
-
const
|
|
867
|
-
const
|
|
868
|
-
const
|
|
869
|
-
const
|
|
870
|
-
const
|
|
872
|
+
const hasEgg = Boolean(raw.have_egg);
|
|
873
|
+
const predictedEggTime = normalizeEpochSeconds(raw.predicted_egg_time);
|
|
874
|
+
const eggReady = hasEgg || (predictedEggTime > 0 && nowTs >= predictedEggTime);
|
|
875
|
+
const feedRound = Number(homePet.feed_round || raw.feed_round || 0) || 0;
|
|
876
|
+
const gender = Number(display.gender || raw.gender || 0) || 0;
|
|
877
|
+
const isMale = gender === 1;
|
|
878
|
+
const status = homePet.status ?? raw.status;
|
|
879
|
+
const isGuard = guard || Boolean(raw.is_guard || raw.guard) || ['2', 'guard', '守卫'].includes(String(status).toLowerCase());
|
|
880
|
+
const hasInspiration = feedRound > 0;
|
|
881
|
+
const inspireReady = hasInspiration;
|
|
882
|
+
const statusText = isGuard && !hasInspiration ? '守卫中'
|
|
883
|
+
: (inspireReady ? '可收取灵感'
|
|
884
|
+
: (hasInspiration ? '灵感收集中'
|
|
885
|
+
: '未喂食'));
|
|
886
|
+
const statusClass = isGuard && !hasInspiration ? 'guard'
|
|
887
|
+
: (eggReady ? 'ready'
|
|
888
|
+
: (inspireReady ? 'progress'
|
|
889
|
+
: (hasInspiration ? 'progress'
|
|
890
|
+
: 'idle')));
|
|
891
|
+
let note;
|
|
892
|
+
if (isGuard && String(petId) === '0') {
|
|
893
|
+
note = '家园守卫位';
|
|
894
|
+
}
|
|
895
|
+
else if (eggReady) {
|
|
896
|
+
note = '可收取';
|
|
897
|
+
}
|
|
898
|
+
else if (predictedEggTime > 0) {
|
|
899
|
+
note = `${formatEggRemaining(predictedEggTime, nowTs)}后生蛋`;
|
|
900
|
+
}
|
|
901
|
+
else if (feedRound > 0) {
|
|
902
|
+
note = isMale ? '' : '等待生蛋';
|
|
903
|
+
}
|
|
904
|
+
else if (isGuard) {
|
|
905
|
+
note = '家园守卫位';
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
note = '未喂食';
|
|
909
|
+
}
|
|
871
910
|
return {
|
|
872
911
|
id: String(petId || ''),
|
|
873
912
|
pos: raw.pos || raw.position || index + 1,
|
|
874
913
|
name: String(homePet.name || homePet.pet_name || raw.name || raw.pet_name || `精灵 ${petId || ''}`),
|
|
875
914
|
level: display.level || raw.level || homePet.level || '--',
|
|
876
915
|
iconUrl: homePetIcon(petId, raw.icon_url || raw.pet_img_url || raw.petIcon || ''),
|
|
877
|
-
badge: isGuard ? '守' : '',
|
|
916
|
+
badge: isGuard ? '守' : (hasEgg ? '蛋' : ''),
|
|
878
917
|
isGuard,
|
|
879
918
|
statusText,
|
|
880
919
|
statusClass,
|
|
881
|
-
note
|
|
882
|
-
inspireReady,
|
|
883
|
-
readyAt,
|
|
920
|
+
note,
|
|
921
|
+
inspireReady: eggReady,
|
|
922
|
+
readyAt: predictedEggTime || 0,
|
|
923
|
+
gender,
|
|
884
924
|
};
|
|
885
925
|
}
|
|
886
926
|
function homePetSources(homeInfo) {
|
|
@@ -1011,6 +1051,18 @@ function buildHomeRenderData(deps, res, uid) {
|
|
|
1011
1051
|
if (item)
|
|
1012
1052
|
guardPets.push(item);
|
|
1013
1053
|
});
|
|
1054
|
+
indoorPets.sort((a, b) => {
|
|
1055
|
+
if (a.gender === 2 && b.gender !== 2)
|
|
1056
|
+
return -1;
|
|
1057
|
+
if (a.gender !== 2 && b.gender === 2)
|
|
1058
|
+
return 1;
|
|
1059
|
+
if (a.gender === 2) {
|
|
1060
|
+
const ta = a.readyAt || Number.MAX_SAFE_INTEGER;
|
|
1061
|
+
const tb = b.readyAt || Number.MAX_SAFE_INTEGER;
|
|
1062
|
+
return ta - tb;
|
|
1063
|
+
}
|
|
1064
|
+
return 0;
|
|
1065
|
+
});
|
|
1014
1066
|
const gardenPlots = extractHomePlants(deps, homeInfo);
|
|
1015
1067
|
const createdAt = normalizeEpochSeconds(res?.meta?.created_at);
|
|
1016
1068
|
return {
|
package/lib/index.js
CHANGED
|
@@ -2667,25 +2667,28 @@ function normalizeEpochSeconds(value) {
|
|
|
2667
2667
|
return Math.floor(ts);
|
|
2668
2668
|
}
|
|
2669
2669
|
__name(normalizeEpochSeconds, "normalizeEpochSeconds");
|
|
2670
|
-
function normalizeDurationSeconds(value) {
|
|
2671
|
-
const seconds = Number(value);
|
|
2672
|
-
if (!Number.isFinite(seconds)) return 0;
|
|
2673
|
-
if (seconds > 1e9) return Math.floor(seconds / 1e6);
|
|
2674
|
-
if (seconds > 1e6) return Math.floor(seconds / 1e3);
|
|
2675
|
-
return Math.floor(seconds);
|
|
2676
|
-
}
|
|
2677
|
-
__name(normalizeDurationSeconds, "normalizeDurationSeconds");
|
|
2678
2670
|
function formatHomeRemaining(targetTs, nowTs = Math.floor(Date.now() / 1e3)) {
|
|
2679
2671
|
if (!targetTs) return "未开始";
|
|
2672
|
+
if (nowTs >= targetTs) return "已完成";
|
|
2680
2673
|
const remain = Math.max(0, targetTs - nowTs);
|
|
2681
|
-
|
|
2682
|
-
const hours = Math.floor(remain / 3600);
|
|
2674
|
+
const days = Math.floor(remain / 86400);
|
|
2675
|
+
const hours = Math.floor(remain % 86400 / 3600);
|
|
2683
2676
|
const minutes = Math.floor(remain % 3600 / 60);
|
|
2684
|
-
|
|
2677
|
+
const seconds = remain % 60;
|
|
2678
|
+
if (days > 0) return hours > 0 ? `${days}天${hours}小时` : `${days}天`;
|
|
2685
2679
|
if (hours > 0) return `${hours}小时${minutes}分钟`;
|
|
2686
|
-
return `${minutes}
|
|
2680
|
+
if (minutes > 0) return `${minutes}分${seconds}秒`;
|
|
2681
|
+
return `${seconds}秒`;
|
|
2687
2682
|
}
|
|
2688
2683
|
__name(formatHomeRemaining, "formatHomeRemaining");
|
|
2684
|
+
function formatEggRemaining(targetTs, nowTs = Math.floor(Date.now() / 1e3)) {
|
|
2685
|
+
if (!targetTs || nowTs >= targetTs) return "0分钟";
|
|
2686
|
+
const remain = Math.max(0, targetTs - nowTs);
|
|
2687
|
+
const totalHours = Math.floor(remain / 3600);
|
|
2688
|
+
const minutes = Math.floor(remain % 3600 / 60);
|
|
2689
|
+
return `${totalHours}小时${minutes}分钟`;
|
|
2690
|
+
}
|
|
2691
|
+
__name(formatEggRemaining, "formatEggRemaining");
|
|
2689
2692
|
function homeInfoPayload(res) {
|
|
2690
2693
|
const payload = res || {};
|
|
2691
2694
|
if (payload.result?.home_info) return payload.result.home_info;
|
|
@@ -3259,30 +3262,47 @@ function extractHomePet(raw, index, guard = false) {
|
|
|
3259
3262
|
const display = raw.display_info && typeof raw.display_info === "object" ? raw.display_info : {};
|
|
3260
3263
|
const petId = homePet.pet_cfg_id || homePet.pet_id || homePet.pet_base_id || raw.pet_cfg_id || raw.pet_id || raw.id;
|
|
3261
3264
|
if (["", "0"].includes(String(petId || "0")) && !guard) return null;
|
|
3262
|
-
const feedInfo = homePet.feed_info && typeof homePet.feed_info === "object" ? homePet.feed_info : {};
|
|
3263
|
-
const beginTime = normalizeEpochSeconds(feedInfo.begin_time);
|
|
3264
|
-
const timeCost = normalizeDurationSeconds(feedInfo.time_cost);
|
|
3265
|
-
let readyAt = normalizeEpochSeconds(homePet.pet_rip_time || raw.pet_rip_time || raw.rip_time);
|
|
3266
|
-
if (!readyAt && beginTime && timeCost) readyAt = beginTime + timeCost;
|
|
3267
3265
|
const nowTs = Math.floor(Date.now() / 1e3);
|
|
3268
|
-
const
|
|
3269
|
-
const
|
|
3270
|
-
const
|
|
3271
|
-
const
|
|
3272
|
-
const
|
|
3266
|
+
const hasEgg = Boolean(raw.have_egg);
|
|
3267
|
+
const predictedEggTime = normalizeEpochSeconds(raw.predicted_egg_time);
|
|
3268
|
+
const eggReady = hasEgg || predictedEggTime > 0 && nowTs >= predictedEggTime;
|
|
3269
|
+
const feedRound = Number(homePet.feed_round || raw.feed_round || 0) || 0;
|
|
3270
|
+
const gender = Number(display.gender || raw.gender || 0) || 0;
|
|
3271
|
+
const isMale = gender === 1;
|
|
3272
|
+
const status = homePet.status ?? raw.status;
|
|
3273
|
+
const isGuard = guard || Boolean(raw.is_guard || raw.guard) || ["2", "guard", "守卫"].includes(String(status).toLowerCase());
|
|
3274
|
+
const hasInspiration = feedRound > 0;
|
|
3275
|
+
const inspireReady = hasInspiration;
|
|
3276
|
+
const statusText = isGuard && !hasInspiration ? "守卫中" : inspireReady ? "可收取灵感" : hasInspiration ? "灵感收集中" : "未喂食";
|
|
3277
|
+
const statusClass = isGuard && !hasInspiration ? "guard" : eggReady ? "ready" : inspireReady ? "progress" : hasInspiration ? "progress" : "idle";
|
|
3278
|
+
let note;
|
|
3279
|
+
if (isGuard && String(petId) === "0") {
|
|
3280
|
+
note = "家园守卫位";
|
|
3281
|
+
} else if (eggReady) {
|
|
3282
|
+
note = "可收取";
|
|
3283
|
+
} else if (predictedEggTime > 0) {
|
|
3284
|
+
note = `${formatEggRemaining(predictedEggTime, nowTs)}后生蛋`;
|
|
3285
|
+
} else if (feedRound > 0) {
|
|
3286
|
+
note = isMale ? "" : "等待生蛋";
|
|
3287
|
+
} else if (isGuard) {
|
|
3288
|
+
note = "家园守卫位";
|
|
3289
|
+
} else {
|
|
3290
|
+
note = "未喂食";
|
|
3291
|
+
}
|
|
3273
3292
|
return {
|
|
3274
3293
|
id: String(petId || ""),
|
|
3275
3294
|
pos: raw.pos || raw.position || index + 1,
|
|
3276
3295
|
name: String(homePet.name || homePet.pet_name || raw.name || raw.pet_name || `精灵 ${petId || ""}`),
|
|
3277
3296
|
level: display.level || raw.level || homePet.level || "--",
|
|
3278
3297
|
iconUrl: homePetIcon(petId, raw.icon_url || raw.pet_img_url || raw.petIcon || ""),
|
|
3279
|
-
badge: isGuard ? "守" : "",
|
|
3298
|
+
badge: isGuard ? "守" : hasEgg ? "蛋" : "",
|
|
3280
3299
|
isGuard,
|
|
3281
3300
|
statusText,
|
|
3282
3301
|
statusClass,
|
|
3283
|
-
note
|
|
3284
|
-
inspireReady,
|
|
3285
|
-
readyAt
|
|
3302
|
+
note,
|
|
3303
|
+
inspireReady: eggReady,
|
|
3304
|
+
readyAt: predictedEggTime || 0,
|
|
3305
|
+
gender
|
|
3286
3306
|
};
|
|
3287
3307
|
}
|
|
3288
3308
|
__name(extractHomePet, "extractHomePet");
|
|
@@ -3399,6 +3419,16 @@ function buildHomeRenderData(deps, res, uid) {
|
|
|
3399
3419
|
const item = extractHomePet(raw, index, true);
|
|
3400
3420
|
if (item) guardPets.push(item);
|
|
3401
3421
|
});
|
|
3422
|
+
indoorPets.sort((a, b) => {
|
|
3423
|
+
if (a.gender === 2 && b.gender !== 2) return -1;
|
|
3424
|
+
if (a.gender !== 2 && b.gender === 2) return 1;
|
|
3425
|
+
if (a.gender === 2) {
|
|
3426
|
+
const ta = a.readyAt || Number.MAX_SAFE_INTEGER;
|
|
3427
|
+
const tb = b.readyAt || Number.MAX_SAFE_INTEGER;
|
|
3428
|
+
return ta - tb;
|
|
3429
|
+
}
|
|
3430
|
+
return 0;
|
|
3431
|
+
});
|
|
3402
3432
|
const gardenPlots = extractHomePlants(deps, homeInfo);
|
|
3403
3433
|
const createdAt = normalizeEpochSeconds(res?.meta?.created_at);
|
|
3404
3434
|
return {
|
|
@@ -4165,6 +4195,47 @@ var TEXT = {
|
|
|
4165
4195
|
defaultSource: "默认",
|
|
4166
4196
|
customSource: "自定义"
|
|
4167
4197
|
};
|
|
4198
|
+
var CATEGORY_ORDER = ["normal", "round", "weekend"];
|
|
4199
|
+
var CATEGORY_LABELS = {
|
|
4200
|
+
normal: "热销商品",
|
|
4201
|
+
round: "常规商品",
|
|
4202
|
+
weekend: "周末限定"
|
|
4203
|
+
};
|
|
4204
|
+
var CHINA_TIMEZONE = "Asia/Shanghai";
|
|
4205
|
+
var chinaPartsFormatter = new Intl.DateTimeFormat("zh-CN", {
|
|
4206
|
+
timeZone: CHINA_TIMEZONE,
|
|
4207
|
+
year: "numeric",
|
|
4208
|
+
month: "2-digit",
|
|
4209
|
+
day: "2-digit",
|
|
4210
|
+
hour: "2-digit",
|
|
4211
|
+
minute: "2-digit",
|
|
4212
|
+
hour12: false
|
|
4213
|
+
});
|
|
4214
|
+
function getChinaParts(timestampMs) {
|
|
4215
|
+
const parts = {};
|
|
4216
|
+
for (const item of chinaPartsFormatter.formatToParts(new Date(timestampMs))) {
|
|
4217
|
+
if (item.type !== "literal") parts[item.type] = item.value;
|
|
4218
|
+
}
|
|
4219
|
+
return {
|
|
4220
|
+
hour: Number(parts.hour || "0"),
|
|
4221
|
+
minute: Number(parts.minute || "0")
|
|
4222
|
+
};
|
|
4223
|
+
}
|
|
4224
|
+
__name(getChinaParts, "getChinaParts");
|
|
4225
|
+
function classifyMerchantItem(item) {
|
|
4226
|
+
const start = normalizeTimestamp(item?.start_time);
|
|
4227
|
+
const end = normalizeTimestamp(item?.end_time);
|
|
4228
|
+
if (!start || !end) return "normal";
|
|
4229
|
+
const durationDays = (end - start) / (1e3 * 60 * 60 * 24);
|
|
4230
|
+
if (durationDays >= 2) return "weekend";
|
|
4231
|
+
const startParts = getChinaParts(start);
|
|
4232
|
+
const endParts = getChinaParts(end);
|
|
4233
|
+
const startHour = startParts.hour + startParts.minute / 60;
|
|
4234
|
+
const endHour = endParts.hour + endParts.minute / 60;
|
|
4235
|
+
if (startHour <= 8 && endHour >= 23.5) return "normal";
|
|
4236
|
+
return "round";
|
|
4237
|
+
}
|
|
4238
|
+
__name(classifyMerchantItem, "classifyMerchantItem");
|
|
4168
4239
|
function normalizeTimestamp(value) {
|
|
4169
4240
|
if (value === null || value === void 0 || value === "") return null;
|
|
4170
4241
|
const timestamp = Number(value);
|
|
@@ -4203,13 +4274,27 @@ function getMerchantActivity(res) {
|
|
|
4203
4274
|
__name(getMerchantActivity, "getMerchantActivity");
|
|
4204
4275
|
function getActiveProducts(res) {
|
|
4205
4276
|
const activity = getMerchantActivity(res);
|
|
4206
|
-
const
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4277
|
+
const groups = [];
|
|
4278
|
+
if (Array.isArray(activity?.products)) groups.push(activity.products);
|
|
4279
|
+
if (Array.isArray(activity?.product_list)) groups.push(activity.product_list);
|
|
4280
|
+
if (Array.isArray(activity?.get_props)) groups.push(activity.get_props);
|
|
4281
|
+
if (Array.isArray(activity?.get_extra_props)) groups.push(activity.get_extra_props);
|
|
4282
|
+
if (Array.isArray(activity?.get_pets)) groups.push(activity.get_pets);
|
|
4283
|
+
const merged = [];
|
|
4284
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4285
|
+
const now = Date.now();
|
|
4286
|
+
for (const list of groups) {
|
|
4287
|
+
for (const item of list) {
|
|
4288
|
+
const start = normalizeTimestamp(item?.start_time) ?? 0;
|
|
4289
|
+
const end = normalizeTimestamp(item?.end_time) ?? Infinity;
|
|
4290
|
+
if (now < start || now >= end) continue;
|
|
4291
|
+
const key = `${item?.id ?? ""}|${item?.name ?? ""}|${start}|${end === Infinity ? "inf" : end}`;
|
|
4292
|
+
if (seen.has(key)) continue;
|
|
4293
|
+
seen.add(key);
|
|
4294
|
+
merged.push(item);
|
|
4295
|
+
}
|
|
4296
|
+
}
|
|
4297
|
+
return merged;
|
|
4213
4298
|
}
|
|
4214
4299
|
__name(getActiveProducts, "getActiveProducts");
|
|
4215
4300
|
function getCurrentMerchantRound() {
|
|
@@ -4284,23 +4369,53 @@ function buildMerchantRenderPayload(res) {
|
|
|
4284
4369
|
const products = getActiveProducts(res);
|
|
4285
4370
|
const roundInfo = getCurrentMerchantRound();
|
|
4286
4371
|
const activity = getMerchantActivity(res);
|
|
4372
|
+
const renderedProducts = products.map((p) => ({
|
|
4373
|
+
name: p.name || TEXT.unknown,
|
|
4374
|
+
image: p.icon_url || "",
|
|
4375
|
+
time_label: formatProductWindow(p),
|
|
4376
|
+
category: classifyMerchantItem(p)
|
|
4377
|
+
}));
|
|
4378
|
+
const categoryMap = {
|
|
4379
|
+
normal: [],
|
|
4380
|
+
round: [],
|
|
4381
|
+
weekend: []
|
|
4382
|
+
};
|
|
4383
|
+
for (const product of renderedProducts) {
|
|
4384
|
+
categoryMap[product.category].push(product);
|
|
4385
|
+
}
|
|
4386
|
+
const categories = CATEGORY_ORDER.filter((key) => categoryMap[key].length > 0).map((key) => ({
|
|
4387
|
+
key,
|
|
4388
|
+
label: CATEGORY_LABELS[key],
|
|
4389
|
+
products: categoryMap[key]
|
|
4390
|
+
}));
|
|
4287
4391
|
const data = {
|
|
4288
4392
|
background: "",
|
|
4289
4393
|
title: activity.name || TEXT.merchant,
|
|
4290
4394
|
subtitle: activity.start_date || "每日 08:00 / 12:00 / 16:00 / 20:00 刷新",
|
|
4291
4395
|
titleIcon: true,
|
|
4292
|
-
product_count:
|
|
4396
|
+
product_count: renderedProducts.length,
|
|
4293
4397
|
round_info: roundInfo,
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
image: p.icon_url || "",
|
|
4297
|
-
time_label: formatProductWindow(p)
|
|
4298
|
-
}))
|
|
4398
|
+
categories,
|
|
4399
|
+
products: renderedProducts
|
|
4299
4400
|
};
|
|
4300
|
-
const
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4401
|
+
const fallbackLines = [];
|
|
4402
|
+
if (renderedProducts.length) {
|
|
4403
|
+
fallbackLines.push(`远行商人当前商品(共 ${renderedProducts.length} 件)`);
|
|
4404
|
+
fallbackLines.push(`轮次:${roundInfo.current || TEXT.notOpen},剩余:${roundInfo.countdown}`);
|
|
4405
|
+
fallbackLines.push("");
|
|
4406
|
+
for (const cat of categories) {
|
|
4407
|
+
fallbackLines.push(`【${cat.label}】`);
|
|
4408
|
+
cat.products.forEach((product, index) => {
|
|
4409
|
+
const tail = product.time_label ? ` (${product.time_label})` : "";
|
|
4410
|
+
fallbackLines.push(` ${index + 1}. ${product.name}${tail}`);
|
|
4411
|
+
});
|
|
4412
|
+
fallbackLines.push("");
|
|
4413
|
+
}
|
|
4414
|
+
} else {
|
|
4415
|
+
fallbackLines.push("当前远行商人暂无商品。");
|
|
4416
|
+
}
|
|
4417
|
+
const fallback = fallbackLines.join("\n").trimEnd();
|
|
4418
|
+
return { products: renderedProducts, roundInfo, data, fallback };
|
|
4304
4419
|
}
|
|
4305
4420
|
__name(buildMerchantRenderPayload, "buildMerchantRenderPayload");
|
|
4306
4421
|
async function checkMerchantSubscriptions(deps) {
|
|
@@ -4631,7 +4746,7 @@ __name(register5, "register");
|
|
|
4631
4746
|
var import_koishi10 = require("koishi");
|
|
4632
4747
|
|
|
4633
4748
|
// src/activities-service.ts
|
|
4634
|
-
var
|
|
4749
|
+
var CHINA_TIMEZONE2 = "Asia/Shanghai";
|
|
4635
4750
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
4636
4751
|
var ACTIVITY_THEMES = ["gold", "green", "brown"];
|
|
4637
4752
|
var LOOKBACK_DAYS = 10;
|
|
@@ -4639,12 +4754,12 @@ var MAX_LOOKAHEAD_DAYS = 50;
|
|
|
4639
4754
|
var TRAILING_DAYS_AFTER_LAST_ACTIVITY = 3;
|
|
4640
4755
|
var MIN_LOOKAHEAD_DAYS = 7;
|
|
4641
4756
|
var chinaDateFormatter = new Intl.DateTimeFormat("zh-CN", {
|
|
4642
|
-
timeZone:
|
|
4757
|
+
timeZone: CHINA_TIMEZONE2,
|
|
4643
4758
|
month: "2-digit",
|
|
4644
4759
|
day: "2-digit"
|
|
4645
4760
|
});
|
|
4646
4761
|
var chinaDateTimeFormatter = new Intl.DateTimeFormat("zh-CN", {
|
|
4647
|
-
timeZone:
|
|
4762
|
+
timeZone: CHINA_TIMEZONE2,
|
|
4648
4763
|
month: "2-digit",
|
|
4649
4764
|
day: "2-digit",
|
|
4650
4765
|
hour: "2-digit",
|
|
@@ -4652,7 +4767,7 @@ var chinaDateTimeFormatter = new Intl.DateTimeFormat("zh-CN", {
|
|
|
4652
4767
|
hour12: false
|
|
4653
4768
|
});
|
|
4654
4769
|
var chinaDatePartsFormatter = new Intl.DateTimeFormat("zh-CN", {
|
|
4655
|
-
timeZone:
|
|
4770
|
+
timeZone: CHINA_TIMEZONE2,
|
|
4656
4771
|
year: "numeric",
|
|
4657
4772
|
month: "2-digit",
|
|
4658
4773
|
day: "2-digit"
|
|
@@ -31,6 +31,16 @@
|
|
|
31
31
|
--card-bg: rgba(255, 252, 247, 0.82);
|
|
32
32
|
--tag-bg: rgba(255, 214, 153, 0.4);
|
|
33
33
|
--tag-text: #9a5f19;
|
|
34
|
+
--accent-color: #a0631d;
|
|
35
|
+
--warning-color: #bf5f3f;
|
|
36
|
+
--border-color: rgba(179, 123, 45, 0.2);
|
|
37
|
+
--border-color-light: rgba(118, 97, 74, 0.14);
|
|
38
|
+
--weekend-color: #c04030;
|
|
39
|
+
--normal-color: #2a8040;
|
|
40
|
+
--round-color: #2a60b0;
|
|
41
|
+
--weekend-bg: rgba(220, 80, 60, 0.15);
|
|
42
|
+
--normal-bg: rgba(60, 160, 80, 0.15);
|
|
43
|
+
--round-bg: rgba(60, 120, 220, 0.15);
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
html,
|
|
@@ -39,7 +49,7 @@ body {
|
|
|
39
49
|
height: auto;
|
|
40
50
|
font-family: var(--font-family);
|
|
41
51
|
color: var(--text-1);
|
|
42
|
-
background: #
|
|
52
|
+
background: linear-gradient(135deg, #fff8eb, #fff0d2);
|
|
43
53
|
}
|
|
44
54
|
|
|
45
55
|
.merchant-page {
|
|
@@ -47,7 +57,7 @@ body {
|
|
|
47
57
|
width: min(1280px, 100%);
|
|
48
58
|
margin: 0 auto;
|
|
49
59
|
overflow: hidden;
|
|
50
|
-
background: #
|
|
60
|
+
background: linear-gradient(135deg, #fff8eb, #fff0d2);
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
.background {
|
|
@@ -133,14 +143,14 @@ body {
|
|
|
133
143
|
padding: 10px 16px;
|
|
134
144
|
border-radius: 999px;
|
|
135
145
|
background: rgba(255,255,255,0.72);
|
|
136
|
-
border: 1px solid
|
|
146
|
+
border: 1px solid var(--border-color);
|
|
137
147
|
color: var(--text-2);
|
|
138
148
|
font-size: 18px;
|
|
139
149
|
}
|
|
140
150
|
|
|
141
151
|
.summary-chip strong {
|
|
142
152
|
font-family: var(--font-family-accent);
|
|
143
|
-
color:
|
|
153
|
+
color: var(--accent-color);
|
|
144
154
|
font-size: 28px;
|
|
145
155
|
margin-left: 8px;
|
|
146
156
|
}
|
|
@@ -163,14 +173,14 @@ body {
|
|
|
163
173
|
}
|
|
164
174
|
|
|
165
175
|
.countdown-pill {
|
|
166
|
-
color:
|
|
176
|
+
color: var(--warning-color);
|
|
167
177
|
font-weight: 700;
|
|
168
178
|
}
|
|
169
179
|
|
|
170
180
|
.products-grid {
|
|
171
|
-
display:
|
|
172
|
-
|
|
173
|
-
gap:
|
|
181
|
+
display: flex;
|
|
182
|
+
flex-direction: column;
|
|
183
|
+
gap: 20px;
|
|
174
184
|
margin-top: 18px;
|
|
175
185
|
}
|
|
176
186
|
|
|
@@ -181,17 +191,17 @@ body {
|
|
|
181
191
|
align-items: center;
|
|
182
192
|
min-height: 148px;
|
|
183
193
|
padding: 18px 28px;
|
|
184
|
-
border: 1px solid rgba(
|
|
194
|
+
border: 1px solid rgba(200, 140, 40, 0.4);
|
|
185
195
|
border-radius: 22px;
|
|
186
|
-
background: linear-gradient(135deg, rgba(255,
|
|
187
|
-
box-shadow: 0
|
|
196
|
+
background: linear-gradient(135deg, rgba(255,248,235,0.88), rgba(255,240,210,0.9));
|
|
197
|
+
box-shadow: 0 6px 22px rgba(180, 120, 20, 0.08);
|
|
188
198
|
}
|
|
189
199
|
|
|
190
200
|
.product-image-container {
|
|
191
201
|
width: 120px;
|
|
192
202
|
height: 120px;
|
|
193
203
|
border-radius: 50%;
|
|
194
|
-
background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.94), rgba(
|
|
204
|
+
background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.94), rgba(255,240,210,0.92));
|
|
195
205
|
display: flex;
|
|
196
206
|
align-items: center;
|
|
197
207
|
justify-content: center;
|
|
@@ -244,8 +254,8 @@ body {
|
|
|
244
254
|
padding: 10px 14px;
|
|
245
255
|
border-radius: 16px;
|
|
246
256
|
background: rgba(255,255,255,0.72);
|
|
247
|
-
border: 1px solid
|
|
248
|
-
color:
|
|
257
|
+
border: 1px solid var(--border-color);
|
|
258
|
+
color: var(--accent-color);
|
|
249
259
|
font-size: 18px;
|
|
250
260
|
font-weight: 700;
|
|
251
261
|
}
|
|
@@ -254,10 +264,53 @@ body {
|
|
|
254
264
|
text-align: center;
|
|
255
265
|
padding: 30px 18px;
|
|
256
266
|
border-radius: 22px;
|
|
257
|
-
border: 1px dashed rgba(
|
|
267
|
+
border: 1px dashed rgba(200, 140, 40, 0.35);
|
|
258
268
|
color: #5c4a37;
|
|
259
269
|
font-size: 22px;
|
|
260
|
-
background: rgba(255,
|
|
270
|
+
background: rgba(255,248,235,0.6);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.category-section {
|
|
274
|
+
margin-top: 16px;
|
|
275
|
+
display: flex;
|
|
276
|
+
flex-direction: column;
|
|
277
|
+
gap: 14px;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.category-header {
|
|
281
|
+
display: flex;
|
|
282
|
+
align-items: center;
|
|
283
|
+
gap: 10px;
|
|
284
|
+
padding: 0 4px;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.category-title {
|
|
288
|
+
font-family: var(--font-family-accent);
|
|
289
|
+
font-size: 22px;
|
|
290
|
+
font-weight: 800;
|
|
291
|
+
color: var(--text-1);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.category-tag {
|
|
295
|
+
padding: 3px 10px;
|
|
296
|
+
border-radius: 999px;
|
|
297
|
+
font-size: 14px;
|
|
298
|
+
font-weight: 700;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.category-tag.weekend {
|
|
302
|
+
background: var(--weekend-bg);
|
|
303
|
+
color: var(--weekend-color);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.category-tag.normal {
|
|
307
|
+
background: var(--normal-bg);
|
|
308
|
+
color: var(--normal-color);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.category-tag.round {
|
|
312
|
+
background: var(--round-bg);
|
|
313
|
+
color: var(--round-color);
|
|
261
314
|
}
|
|
262
315
|
|
|
263
316
|
@media (max-width: 760px) {
|
|
@@ -345,20 +398,24 @@ body {
|
|
|
345
398
|
</div>
|
|
346
399
|
|
|
347
400
|
<div class="products-grid">
|
|
348
|
-
{{if
|
|
349
|
-
{{each
|
|
350
|
-
<div class="
|
|
351
|
-
<div class="
|
|
352
|
-
<
|
|
353
|
-
|
|
354
|
-
<div class="product-main">
|
|
355
|
-
<div class="product-name">{{product.name}}</div>
|
|
356
|
-
<div class="product-sub">远行商人当前轮次商品</div>
|
|
357
|
-
<div class="product-time">北京时间 {{product.time_label}}</div>
|
|
401
|
+
{{if categories && categories.length > 0}}
|
|
402
|
+
{{each categories cat}}
|
|
403
|
+
<div class="category-section">
|
|
404
|
+
<div class="category-header">
|
|
405
|
+
<span class="category-title">{{cat.label}}</span>
|
|
406
|
+
<span class="category-tag {{cat.key}}">{{cat.products.length}} 件</span>
|
|
358
407
|
</div>
|
|
359
|
-
|
|
360
|
-
|
|
408
|
+
{{each cat.products product}}
|
|
409
|
+
<div class="product-card">
|
|
410
|
+
<div class="product-image-container">
|
|
411
|
+
<img class="product-image" src="{{product.image}}" alt="{{product.name}}">
|
|
412
|
+
</div>
|
|
413
|
+
<div class="product-main">
|
|
414
|
+
<div class="product-name">{{product.name}}</div>
|
|
415
|
+
<div class="product-time">北京时间 {{product.time_label}}</div>
|
|
416
|
+
</div>
|
|
361
417
|
</div>
|
|
418
|
+
{{/each}}
|
|
362
419
|
</div>
|
|
363
420
|
{{/each}}
|
|
364
421
|
{{else}}
|
package/package.json
CHANGED