koishi-plugin-rocom 1.0.4 → 1.0.5

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/client.js CHANGED
@@ -126,7 +126,7 @@ class RocomClient {
126
126
  stack: err?.stack,
127
127
  } : undefined,
128
128
  };
129
- console.warn(`[rocom-client] ${method} ${path} detailed failure\n${this.stringifyForLog(details)}`);
129
+ logger.warn(`${method} ${path} detailed failure\n${this.stringifyForLog(details)}`);
130
130
  }
131
131
  async get(ctx, path, headers, params, options) {
132
132
  try {
@@ -161,9 +161,26 @@ function register(deps) {
161
161
  .action(async ({ session }, index) => {
162
162
  if (!index)
163
163
  return '用法:洛克.切换 <序号>';
164
- return userMgr.switchPrimary(session.userId, index)
165
- ? `成功切换到序号 ${index} 账号。`
166
- : '序号无效。';
164
+ const userId = session.userId;
165
+ if (!userMgr.switchPrimary(userId, index))
166
+ return '序号无效。';
167
+ const newPrimary = userMgr.getPrimaryBinding(userId);
168
+ if (newPrimary?.binding_id) {
169
+ const token = await (0, role_token_1.getRoleToken)(ctx, userId);
170
+ if (token && token.bindingId !== newPrimary.binding_id) {
171
+ const res = await client.refreshBinding(ctx, newPrimary.binding_id, userId);
172
+ if (res?.framework_token) {
173
+ await (0, role_token_1.upsertRoleToken)(ctx, {
174
+ userId,
175
+ fwt: res.framework_token,
176
+ bindingId: newPrimary.binding_id,
177
+ roleId: newPrimary.role_id,
178
+ loginType: newPrimary.login_type,
179
+ });
180
+ }
181
+ }
182
+ }
183
+ return `成功切换到序号 ${index} 账号:${newPrimary?.nickname || '未知'}`;
167
184
  });
168
185
  ctx.command('洛克').subcommand('.解绑 <index:number>', '解绑账号')
169
186
  .alias('洛克解绑')
@@ -184,6 +201,27 @@ function register(deps) {
184
201
  if (userMgr.getUserBindings(session.userId).length === 0) {
185
202
  await (0, role_token_1.removeRoleToken)(ctx, session.userId);
186
203
  }
204
+ else {
205
+ const token = await (0, role_token_1.getRoleToken)(ctx, session.userId);
206
+ if (token?.bindingId === removed.binding_id) {
207
+ const newPrimary = userMgr.getPrimaryBinding(session.userId);
208
+ if (newPrimary?.binding_id) {
209
+ const res = await client.refreshBinding(ctx, newPrimary.binding_id, session.userId);
210
+ if (res?.framework_token) {
211
+ await (0, role_token_1.upsertRoleToken)(ctx, {
212
+ userId: session.userId,
213
+ fwt: res.framework_token,
214
+ bindingId: newPrimary.binding_id,
215
+ roleId: newPrimary.role_id,
216
+ loginType: newPrimary.login_type,
217
+ });
218
+ }
219
+ else {
220
+ await (0, role_token_1.removeRoleToken)(ctx, session.userId);
221
+ }
222
+ }
223
+ }
224
+ }
187
225
  return `已解绑账号:${removed.nickname}`;
188
226
  });
189
227
  ctx.command('洛克').subcommand('.刷新', '刷新当前主账号凭证')
@@ -53,19 +53,33 @@ function register(deps) {
53
53
  const allUsers = userMgr.getAllUsersBindings();
54
54
  let totalInvalid = 0;
55
55
  let totalValid = 0;
56
+ let totalSkipped = 0;
56
57
  for (const [userId, bindings] of Object.entries(allUsers)) {
57
58
  const token = await (0, role_token_1.getRoleToken)(ctx, userId);
58
59
  if (!token?.fwt) {
59
60
  logger.warn(`用户 ${userId} 没有可用的 fwt,跳过失效检测`);
61
+ totalSkipped += bindings.length;
60
62
  continue;
61
63
  }
62
- const fwToken = token.fwt;
63
64
  const valid = [];
64
65
  for (const binding of bindings) {
65
- const roleRes = await client.getRole(ctx, fwToken, undefined, userId);
66
+ const refreshRes = binding.binding_id
67
+ ? await client.refreshBinding(ctx, binding.binding_id, userId)
68
+ : null;
69
+ const fwt = refreshRes?.framework_token || token.fwt;
70
+ const roleRes = await client.getRole(ctx, fwt, undefined, userId);
66
71
  if (roleRes?.role) {
67
72
  valid.push(binding);
68
73
  totalValid++;
74
+ if (refreshRes?.framework_token) {
75
+ await (0, role_token_1.upsertRoleToken)(ctx, {
76
+ userId,
77
+ fwt: refreshRes.framework_token,
78
+ bindingId: binding.binding_id,
79
+ roleId: binding.role_id,
80
+ loginType: binding.login_type,
81
+ });
82
+ }
69
83
  continue;
70
84
  }
71
85
  if (binding.binding_id) {
@@ -83,16 +97,24 @@ function register(deps) {
83
97
  await (0, role_token_1.removeRoleToken)(ctx, userId);
84
98
  }
85
99
  }
86
- return totalInvalid > 0
87
- ? `清理完成:移除 ${totalInvalid} 个无效绑定,剩余 ${totalValid} 个有效绑定。`
100
+ const parts = [`清理完成:移除 ${totalInvalid} 个无效绑定,剩余 ${totalValid} 个有效绑定。`];
101
+ if (totalSkipped)
102
+ parts.push(`跳过 ${totalSkipped} 个(无可用凭证)。`);
103
+ return totalInvalid > 0 || totalSkipped > 0
104
+ ? parts.join('')
88
105
  : `所有绑定均有效,无需清理。共 ${totalValid} 个有效绑定。`;
89
106
  });
90
107
  if (config.autoRefreshEnabled) {
108
+ let lastRefreshKey = '';
91
109
  ctx.setInterval(async () => {
92
110
  const now = new Date();
93
111
  const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
94
112
  if (!config.autoRefreshTime.includes(timeStr))
95
113
  return;
114
+ const refreshKey = `${now.toDateString()}-${timeStr}`;
115
+ if (refreshKey === lastRefreshKey)
116
+ return;
117
+ lastRefreshKey = refreshKey;
96
118
  const allUsers = userMgr.getAllUsersBindings();
97
119
  for (const [userId] of Object.entries(allUsers)) {
98
120
  const binding = userMgr.getPrimaryBinding(userId);
@@ -114,6 +136,6 @@ function register(deps) {
114
136
  logger.warn(`自动刷新用户 ${userId} 失败: ${e}`);
115
137
  }
116
138
  }
117
- }, 60000);
139
+ }, 30000);
118
140
  }
119
141
  }
@@ -28,17 +28,25 @@ function formatProductWindow(product) {
28
28
  const end = normalizeTimestamp(product?.end_time);
29
29
  if (!start && !end)
30
30
  return '';
31
+ const formatDate = (timestamp) => {
32
+ const date = new Date(timestamp);
33
+ const month = String(date.getMonth() + 1).padStart(2, '0');
34
+ const day = String(date.getDate()).padStart(2, '0');
35
+ return `${month}-${day}`;
36
+ };
31
37
  const formatTime = (timestamp) => {
32
38
  const date = new Date(timestamp);
33
39
  const hour = String(date.getHours()).padStart(2, '0');
34
40
  const minute = String(date.getMinutes()).padStart(2, '0');
35
41
  return `${hour}:${minute}`;
36
42
  };
37
- if (start && end)
38
- return `${formatTime(start)}-${formatTime(end)}`;
43
+ if (start && end) {
44
+ const datePart = formatDate(start);
45
+ return `${datePart} ${formatTime(start)} – ${formatTime(end)}`;
46
+ }
39
47
  if (start)
40
- return `${formatTime(start)}+`;
41
- return `${formatTime(end)}-`;
48
+ return `${formatDate(start)} ${formatTime(start)}+`;
49
+ return `${formatDate(end)} ${formatTime(end)}-`;
42
50
  }
43
51
  function getMerchantActivity(res) {
44
52
  const activities = res?.merchantActivities || res?.merchant_activities || [];
@@ -83,6 +91,7 @@ function getCurrentMerchantRound() {
83
91
  ].join('-');
84
92
  return {
85
93
  current: currentRound,
94
+ total: rounds.length,
86
95
  countdown: `${hours}\u5c0f\u65f6${mins}\u5206\u949f`,
87
96
  is_open: currentRound !== null,
88
97
  round_id: `${datePart}-${currentRound || 'closed'}`,
@@ -114,14 +123,37 @@ function isBotAdmin(session, adminUserIds) {
114
123
  function sameStringArray(left, right) {
115
124
  return JSON.stringify(left) === JSON.stringify(right);
116
125
  }
126
+ function buildMerchantRenderPayload(res) {
127
+ const products = getActiveProducts(res);
128
+ const roundInfo = getCurrentMerchantRound();
129
+ const activity = getMerchantActivity(res);
130
+ const data = {
131
+ background: '',
132
+ title: activity.name || TEXT.merchant,
133
+ subtitle: activity.start_date || '\u6bcf\u65e5 08:00 / 12:00 / 16:00 / 20:00 \u5237\u65b0',
134
+ titleIcon: true,
135
+ product_count: products.length,
136
+ round_info: roundInfo,
137
+ products: products.map((p) => ({
138
+ name: p.name || TEXT.unknown,
139
+ image: p.icon_url || '',
140
+ time_label: formatProductWindow(p),
141
+ })),
142
+ };
143
+ const fallback = products.length
144
+ ? `\u8fdc\u884c\u5546\u4eba\u5f53\u524d\u5546\u54c1\uff1a${products.map((p) => p.name || TEXT.unknown).join('\u3001')}\n\u8f6e\u6b21\uff1a${roundInfo.current || TEXT.notOpen}\n\u5269\u4f59\uff1a${roundInfo.countdown}`
145
+ : '\u5f53\u524d\u8fdc\u884c\u5546\u4eba\u6682\u65e0\u5546\u54c1\u3002';
146
+ return { products, roundInfo, data, fallback };
147
+ }
117
148
  async function checkMerchantSubscriptions(deps) {
118
- const { ctx, client, merchantSubMgr } = deps;
149
+ const { ctx, client, merchantSubMgr, renderer, config } = deps;
119
150
  const res = await client.getMerchantInfo(ctx, true);
120
151
  if (!res)
121
152
  return { subscriptions: 0, matched: 0, pushed: 0 };
122
- const products = getActiveProducts(res);
153
+ const { products, roundInfo, data, fallback } = buildMerchantRenderPayload(res);
123
154
  const productNames = products.map((p) => p.name || '').filter(Boolean);
124
- const roundInfo = getCurrentMerchantRound();
155
+ const rendered = await renderer.renderHtml(ctx, 'yuanxing-shangren', data);
156
+ const renderedPng = rendered ? (0, send_image_1.compressPngImage)(rendered, config) : null;
125
157
  const subs = merchantSubMgr.getAll();
126
158
  let matchedCount = 0;
127
159
  let pushedCount = 0;
@@ -139,12 +171,13 @@ async function checkMerchantSubscriptions(deps) {
139
171
  logger.warn(`\u63a8\u9001\u5931\u8d25 ${key}: \u65e0\u6cd5\u786e\u5b9a\u5e73\u53f0\u6216\u9891\u9053`);
140
172
  continue;
141
173
  }
142
- const sent = await (0, subscription_send_1.sendScheduledMessage)(ctx, {
174
+ const fallbackText = `${msg}\n${fallback}`;
175
+ const sent = await (0, subscription_send_1.sendScheduledImageWithFallback)(ctx, {
143
176
  platform,
144
177
  channelId,
145
178
  guildId: sub.group_id || '',
146
179
  userId: sub.user_id || '',
147
- }, sub.mention_all ? `@\u5168\u4f53\n${msg}` : msg);
180
+ }, renderedPng, fallbackText, !!sub.mention_all);
148
181
  if (!sent)
149
182
  continue;
150
183
  pushedCount++;
@@ -163,25 +196,7 @@ function register(deps) {
163
196
  const res = await client.getMerchantInfo(ctx, true);
164
197
  if (!res)
165
198
  return '\u83b7\u53d6\u8fdc\u884c\u5546\u4eba\u6570\u636e\u5931\u8d25\u3002';
166
- const products = getActiveProducts(res);
167
- const roundInfo = getCurrentMerchantRound();
168
- const activity = getMerchantActivity(res);
169
- const data = {
170
- background: '',
171
- title: activity.name || TEXT.merchant,
172
- subtitle: activity.start_date || '\u6bcf\u65e5 08:00 / 12:00 / 16:00 / 20:00 \u5237\u65b0',
173
- titleIcon: '',
174
- product_count: products.length,
175
- round_info: roundInfo,
176
- products: products.map((p) => ({
177
- name: p.name || TEXT.unknown,
178
- image: p.icon_url || '',
179
- time_label: formatProductWindow(p),
180
- })),
181
- };
182
- const fallback = products.length
183
- ? `\u8fdc\u884c\u5546\u4eba\u5f53\u524d\u5546\u54c1\uff1a${products.map((p) => p.name || TEXT.unknown).join('\u3001')}\n\u8f6e\u6b21\uff1a${roundInfo.current || TEXT.notOpen}\n\u5269\u4f59\uff1a${roundInfo.countdown}`
184
- : '\u5f53\u524d\u8fdc\u884c\u5546\u4eba\u6682\u65e0\u5546\u54c1\u3002';
199
+ const { data, fallback } = buildMerchantRenderPayload(res);
185
200
  const png = await deps.renderer.renderHtml(ctx, 'yuanxing-shangren', data);
186
201
  await (0, send_image_1.sendImageWithFallback)(session, png, fallback, 'merchant:yuanxing-shangren', deps.config);
187
202
  });