koishi-plugin-stock 2.0.6 → 2.0.8

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 (3) hide show
  1. package/README.md +36 -29
  2. package/lib/index.js +145 -126
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -57,106 +57,113 @@ npm install koishi-plugin-stock
57
57
 
58
58
  ## 更新日志
59
59
 
60
- ### v2.0.6
60
+ ### v2.0.8 (2026-01-30)
61
+ - 优化黑名单处理逻辑:当触发指令的用户或频道处于黑名单时,插件将静默处理(直接不回复),不再发送提示消息。
62
+
63
+ ### v2.0.7 (2026-01-30)
64
+ - 新增定时广播任务失败重试机制:当网络中断导致消息发送失败时,自动开启 3 次重试,每次间隔 1 分钟
65
+ - 引入任务时效性检查:重试过程中若达到该任务的下一个执行时间点,则自动取消当前重试,防止消息堆积
66
+
67
+ ### v2.0.6 (2026-01-30)
61
68
  - 优化定时任务触发逻辑:改用 `Intl` API 获取确切的上海时区时间,解决部分环境下时区偏移导致的触发失败问题
62
69
  - 增强时间格式容错:自动处理 `9:30` vs `09:30` 以及全角冒号等配置问题
63
70
  - 完善节假日 API:增加对 Cloudflare 拦截页面的防御逻辑,请求失败时自动回退到基础周末检查,确保任务触发稳定性
64
71
  - 改进日志输出:在 `info` 级别增加关键任务触发和跳过的日志,方便诊断问题
65
72
 
66
- ### v2.0.5
73
+ ### v2.0.5 (2026-01-28)
67
74
  - 使用标准 Koishi 配置注释、每个配置分类上壳有中文注释,更清晰地区分配置组
68
75
 
69
- ### v2.0.4
76
+ ### v2.0.4 (2026-01-28)
70
77
  - 实现 Koishi 业界标准的配置分组,每个配置项打上对应的 role 标签,在插件设置页中形成可折叠的分组
71
78
 
72
- ### v2.0.3
79
+ ### v2.0.3 (2026-01-28)
73
80
  - 优化插件配置页显示,使用 Koishi 行业标准方式实现配置分类,每个配置项描述前缀形成分类标签
74
81
 
75
- ### v2.0.2
82
+ ### v2.0.2 (2026-01-28)
76
83
  - 优化插件配置页显示,描述中添加配置分类标签(用户黑名单、频道黑名单、定时广播)
77
84
 
78
- ### v2.0.1
85
+ ### v2.0.1 (2026-01-28)
79
86
  - 优化插件设置页配置展示,按功能分类整理配置项(系统设置、用户黑名单、频道黑名单、定时广播)
80
87
 
81
- ### v2.0.0
88
+ ### v2.0.0 (2026-01-28)
82
89
  - 修复时区问题:Node.js 返回 UTC 时间而非本地时间,手动调整 UTC+8 获取中国时间
83
90
  - 定时任务功能现已正常工作
84
91
  - 添加 `enableDebugLog` 配置选项,调试日志默认关闭,需要时可启用
85
92
  - 清理历史有问题的旧版本
86
93
 
87
- ### v1.0.22
94
+ ### v1.0.22 (2026-01-28)
88
95
  - 修复时区问题:手动调整 UTC+8 转换为中国本地时间
89
96
  - 优化交易日检查的时区不一致问题
90
97
 
91
- ### v1.0.21
98
+ ### v1.0.21 (2026-01-28)
92
99
  - 添加调试信息用于时间不匹配问题诊断
93
100
 
94
- ### v1.0.20
101
+ ### v1.0.20 (2026-01-28)
95
102
  - 修复定时任务检查间隔需要重新编译的问题
96
103
  - 改为 1 秒检查一次,使用 logger.info 输出日志
97
104
 
98
- ### v1.0.19
105
+ ### v1.0.19 (2026-01-28)
99
106
  - 修复定时任务 1 分钟检查一次的问题,改为 1 秒检查一次,确保不会错过任务时间
100
107
  - 日志级别改为 info,方便查看日志
101
108
 
102
- ### v1.0.18
109
+ ### v1.0.18 (2026-01-28)
103
110
  - 添加详细的执行日志,追踪图片下载、消息发送的全过程
104
111
  - 添加机器人信息输出,便于调试发送失败
105
112
 
106
- ### v1.0.17
113
+ ### v1.0.17 (2026-01-28)
107
114
  - 修复广播任务配置中 `content` 字段不保存的问题
108
115
  - 添加 `content` 字段的默认值:活跃市值
109
116
 
110
- ### v1.0.16
117
+ ### v1.0.16 (2026-01-28)
111
118
  - 添加插件启动日志,方便确认插件是否正常加载
112
119
  - 添加定时任务运行状态日志,方便调试
113
120
 
114
- ### v1.0.15
121
+ ### v1.0.15 (2026-01-28)
115
122
  - 修复定时广播任务不发送的问题
116
123
  - 优化配置验证逻辑,添加了详细的错误日志
117
124
  - 改进了 API 调用的错误处理,单个API失败不会中断整个任务执行
118
125
 
119
- ### v1.0.14
126
+ ### v1.0.14 (2026-01-28)
120
127
  - 改进定时广播配置,支持一个任务配置多个触发时间
121
128
  - 支持指定多个目标 ID,支持一次广播给多个用户/群组
122
129
  - 优化了广播发送逻辑,增强了错误处理能力
123
130
 
124
- ### v1.0.13
131
+ ### v1.0.13 (2026-01-27)
125
132
  - 新增定时广播任务功能,支持自定义时间发送指数和看板数据
126
133
  - 自动识别中国交易日(排除周末及法定节假日)
127
134
 
128
- ### v1.0.12
135
+ ### v1.0.12 (2026-01-27)
129
136
  - 添加了"跌停看板"指令,可获取跌停股票看板图片
130
137
 
131
- ### v1.0.11
138
+ ### v1.0.11 (2026-01-22)
132
139
  - 添加了频道/群聊黑名单功能,支持按频道ID限制指令使用
133
140
 
134
- ### v1.0.10
141
+ ### v1.0.10 (2026-01-22)
135
142
  - 修复了"骑"指令,使用本地图片路径并返回base64编码的图片
136
143
 
137
- ### v1.0.9
144
+ ### v1.0.9 (2026-01-22)
138
145
  - 添加了"骑"指令,可返回本地图片
139
146
 
140
- ### v1.0.8
147
+ ### v1.0.8 (2026-01-22)
141
148
  - 添加了指令级黑名单功能,可为每个指令单独设置黑名单
142
149
 
143
- ### v1.0.7
150
+ ### v1.0.7 (2026-01-22)
144
151
  - 修复了一些小问题
145
152
 
146
- ### v1.0.6
153
+ ### v1.0.6 (2026-01-22)
147
154
  - 优化了部分功能
148
155
 
149
- ### v1.0.5
156
+ ### v1.0.5 (2026-01-22)
150
157
  - 添加了选股功能的数字编号支持
151
158
 
152
- ### v1.0.4
159
+ ### v1.0.4 (2026-01-22)
153
160
  - 添加了选股功能的数字编号(1-6对应不同策略)
154
161
 
155
- ### v1.0.3
162
+ ### v1.0.3 (2026-01-22)
156
163
  - 添加了选股功能,支持多种选股策略
157
164
 
158
- ### v1.0.2
165
+ ### v1.0.2 (2026-01-22)
159
166
  - 添加了涨停看板功能
160
167
 
161
- ### v1.0.1
168
+ ### v1.0.1 (2026-01-22)
162
169
  - 初始版本,包含活跃市值和异动分析功能
package/lib/index.js CHANGED
@@ -40,6 +40,7 @@ exports.Config = koishi_1.Schema.object({
40
40
  });
41
41
  function apply(ctx, config) {
42
42
  const logger = ctx.logger('stock');
43
+ const retryQueue = [];
43
44
  // 插件启动日志
44
45
  logger.info('stock 插件已加载');
45
46
  logger.info(`当前配置: broadcastTasks=${config.broadcastTasks?.length || 0} 个任务`);
@@ -48,167 +49,185 @@ function apply(ctx, config) {
48
49
  logger.info(`任务 ${idx + 1}: times=${task.times}, type=${task.type}, targetIds=${task.targetIds}, content=${task.content}`);
49
50
  });
50
51
  }
52
+ // 核心广播执行函数
53
+ async function performBroadcast(task, isRetry = false) {
54
+ const label = isRetry ? '[重试广播]' : '[初始广播]';
55
+ try {
56
+ if (!task.targetIds || typeof task.targetIds !== 'string') {
57
+ logger.warn(`${label} 任务配置不完整: targetIds 字段无效`);
58
+ return false;
59
+ }
60
+ const targetIds = task.targetIds.split(/[,,]/).map(id => id.trim()).filter(id => id);
61
+ if (targetIds.length === 0) {
62
+ logger.warn(`${label} 任务目标列表为空: ${task.content}`);
63
+ return false;
64
+ }
65
+ logger.info(`${label} 正在执行: ${task.content} -> ${targetIds.join(',')} (${task.type})`);
66
+ let message = '';
67
+ // 1. 获取内容
68
+ if (task.content === '活跃市值') {
69
+ try {
70
+ const responseText = await ctx.http.get('http://stock.svip886.com/api/indexes', { responseType: 'text' });
71
+ message = `📊 定时广播 - 指数看板:\n\n${responseText}`;
72
+ }
73
+ catch (apiErr) {
74
+ logger.error(`${label} 获取活跃市值 API 失败`, apiErr);
75
+ return false;
76
+ }
77
+ }
78
+ else if (task.content === '涨停看板' || task.content === '跌停看板') {
79
+ try {
80
+ const apiType = task.content === '涨停看板' ? 'limit_up' : 'limit_down';
81
+ const imageUrl = `http://stock.svip886.com/api/${apiType}.png`;
82
+ const imageBuffer = await ctx.http.get(imageUrl, { responseType: 'arraybuffer' });
83
+ const base64Image = Buffer.from(imageBuffer).toString('base64');
84
+ message = `🔔 定时广播 - ${task.content}:\n<img src="data:image/png;base64,${base64Image}" />`;
85
+ }
86
+ catch (apiErr) {
87
+ logger.error(`${label} 获取${task.content}图片 API 失败`, apiErr);
88
+ return false;
89
+ }
90
+ }
91
+ if (!message)
92
+ return false;
93
+ // 2. 发送消息
94
+ const bot = ctx.bots.find(b => b.status === 'online' || b.status === 1) || ctx.bots[0];
95
+ if (!bot) {
96
+ logger.error(`${label} 无可用机器人实例`);
97
+ return false;
98
+ }
99
+ let allSuccess = true;
100
+ for (const targetId of targetIds) {
101
+ try {
102
+ if (task.type === 'private') {
103
+ await bot.sendPrivateMessage(targetId, message);
104
+ }
105
+ else {
106
+ await bot.sendMessage(targetId, message);
107
+ }
108
+ }
109
+ catch (err) {
110
+ logger.error(`${label} 发送失败 (${targetId}): ${err.message}`);
111
+ allSuccess = false;
112
+ }
113
+ }
114
+ return allSuccess;
115
+ }
116
+ catch (error) {
117
+ logger.error(`${label} 执行过程发生严重错误`, error);
118
+ return false;
119
+ }
120
+ }
121
+ // 获取任务的下一个执行时间点
122
+ function getNextScheduledTime(task, currentTime) {
123
+ const times = task.times.split(/[,,]/).map(s => {
124
+ let time = s.trim();
125
+ if (/^\d:\d{2}$/.test(time))
126
+ time = '0' + time;
127
+ return time;
128
+ }).sort();
129
+ const currentIndex = times.indexOf(currentTime);
130
+ if (currentIndex === -1)
131
+ return times[0]; // 如果当前时间不在列表里,假设下一个是第一个
132
+ return times[(currentIndex + 1) % times.length];
133
+ }
51
134
  // 定时任务逻辑
52
135
  let lastCheckedMinute = '';
53
136
  ctx.setInterval(async () => {
54
137
  const now = new Date();
55
- // 使用 Intl API 获取确切的中国时间,避免手动时区转换的误差
138
+ const nowTimestamp = now.getTime();
56
139
  const chinaTimeStr = now.toLocaleTimeString('zh-CN', {
57
140
  timeZone: 'Asia/Shanghai',
58
141
  hour12: false,
59
142
  hour: '2-digit',
60
143
  minute: '2-digit'
61
144
  });
62
- // 确保格式为 HH:mm,并处理可能的中文冒号
63
145
  const currentTime = chinaTimeStr.replace(':', ':');
64
- // 每分钟的第一次执行时打印当前时间
65
146
  if (config.enableDebugLog && currentTime !== lastCheckedMinute) {
66
147
  logger.info(`[定时任务检查] 当前时间: ${currentTime} (上海时区)`);
67
148
  }
149
+ // --- 1. 处理重试队列 ---
150
+ for (let i = retryQueue.length - 1; i >= 0; i--) {
151
+ const item = retryQueue[i];
152
+ // 检查是否达到1分钟间隔
153
+ if (nowTimestamp - item.lastAttemptTimestamp < 60000)
154
+ continue;
155
+ // 检查是否超过了下次执行时间
156
+ const nextTime = getNextScheduledTime(item.task, item.originalTime);
157
+ // 如果当前时间已经达到了该任务的下一个预定时间,或者已经跨天了,则取消重试
158
+ if (nextTime && (currentTime === nextTime || (currentTime > nextTime && item.originalTime < nextTime))) {
159
+ logger.warn(`[重试取消] 任务 ${item.task.content} (${item.originalTime}) 已过期或达到下个周期`);
160
+ retryQueue.splice(i, 1);
161
+ continue;
162
+ }
163
+ item.retryCount++;
164
+ item.lastAttemptTimestamp = nowTimestamp;
165
+ logger.info(`[开始重试] 任务 ${item.task.content} (原定于 ${item.originalTime}), 第 ${item.retryCount}/3 次重试`);
166
+ const success = await performBroadcast(item.task, true);
167
+ if (success) {
168
+ logger.info(`[重试成功] 任务 ${item.task.content} 已成功补发`);
169
+ retryQueue.splice(i, 1);
170
+ }
171
+ else if (item.retryCount >= 3) {
172
+ logger.error(`[重试失败] 任务 ${item.task.content} 已达到最大重试次数,放弃`);
173
+ retryQueue.splice(i, 1);
174
+ }
175
+ }
176
+ // --- 2. 处理初始任务 ---
68
177
  if (currentTime === lastCheckedMinute)
69
178
  return;
70
179
  if (!config.broadcastTasks || config.broadcastTasks.length === 0)
71
180
  return;
72
- // 检查当前时间是否有任务
73
181
  const activeTasks = config.broadcastTasks.filter(t => {
74
- if (!t.times || typeof t.times !== 'string') {
75
- logger.warn(`跳过配置不正确的任务: times 字段无效`);
182
+ if (!t.times || typeof t.times !== 'string')
76
183
  return false;
77
- }
78
- // 支持中英文逗号,并自动补全 9:30 这种格式为 09:30
79
184
  const times = t.times.split(/[,,]/).map(s => {
80
185
  let time = s.trim();
81
186
  if (/^\d:\d{2}$/.test(time))
82
187
  time = '0' + time;
83
188
  return time;
84
189
  }).filter(s => s);
85
- const isMatch = times.includes(currentTime);
86
- if (config.enableDebugLog && isMatch) {
87
- logger.info(`[时间匹配成功] 当前时间 ${currentTime} 命中任务设定列表 [${times.join(',')}]`);
88
- }
89
- return isMatch;
190
+ return times.includes(currentTime);
90
191
  });
91
- if (activeTasks.length === 0)
192
+ if (activeTasks.length === 0) {
193
+ if (currentTime !== lastCheckedMinute)
194
+ lastCheckedMinute = currentTime;
92
195
  return;
196
+ }
93
197
  lastCheckedMinute = currentTime;
94
- // 即使在 info 级别也打印匹配到的任务,方便用户确认
95
- logger.info(`[任务触发] 检测到 ${activeTasks.length} 个待执行的广播任务 (时间: ${currentTime})`);
198
+ logger.info(`[任务触发] 命中 ${activeTasks.length} 个广播任务 (时间: ${currentTime})`);
96
199
  try {
97
- // 检查是否为交易日(基本周末检查 + 节假日API)
98
- // 使用 Intl API 获取上海日期的组件,确保逻辑一致
99
- const formatter = new Intl.DateTimeFormat('zh-CN', {
100
- timeZone: 'Asia/Shanghai',
101
- year: 'numeric',
102
- month: '2-digit',
103
- day: '2-digit',
104
- weekday: 'narrow'
105
- });
106
- const parts = formatter.formatToParts(now);
107
- const getValue = (type) => parts.find(p => p.type === type)?.value || '';
108
- const year = getValue('year');
109
- const month = getValue('month');
110
- const day = getValue('day');
111
200
  const shanghaiDate = new Date(now.toLocaleString('en-US', { timeZone: 'Asia/Shanghai' }));
112
201
  const isWeekend = (shanghaiDate.getDay() === 0 || shanghaiDate.getDay() === 6);
202
+ const year = shanghaiDate.getFullYear();
203
+ const month = (shanghaiDate.getMonth() + 1).toString().padStart(2, '0');
204
+ const day = shanghaiDate.getDate().toString().padStart(2, '0');
113
205
  const dateStr = `${year}-${month}-${day}`;
114
206
  let tradingDay = !isWeekend;
115
207
  try {
116
- if (config.enableDebugLog)
117
- logger.debug(`正在检查交易日状态: ${dateStr}`);
118
208
  const holidayData = await ctx.http.get(`https://timor.tech/api/holiday/info/${dateStr}`);
119
- // 增加对 HTML 响应的防御(Cloudflare 拦截时会返回 HTML 字符串)
120
209
  if (holidayData && typeof holidayData === 'object' && holidayData.type) {
121
- // type: 0 工作日, 1 周末, 2 节日, 3 调休
122
210
  const typeCode = holidayData.type.type;
123
211
  tradingDay = (typeCode === 0 || typeCode === 3);
124
- logger.info(`节假日API返回类型: ${typeCode} (${holidayData.type.name}), 交易日状态: ${tradingDay}`);
125
- }
126
- else {
127
- if (config.enableDebugLog)
128
- logger.warn(`节假日API返回了非预期的格式或被拦截,将回退到周末检查`);
129
212
  }
130
213
  }
131
214
  catch (e) {
132
- logger.warn(`节假日API请求失败,将使用基础周末检查: ${e.message}`);
215
+ logger.warn(`节假日API请求失败: ${e.message}`);
133
216
  }
134
217
  if (!tradingDay) {
135
- logger.info(`[定时任务跳过] 当前日期 ${dateStr} 非交易日,跳过执行`);
218
+ logger.info(`[定时任务跳过] ${dateStr} 非交易日`);
136
219
  return;
137
220
  }
138
221
  for (const task of activeTasks) {
139
- try {
140
- // 验证配置完整性
141
- if (!task.targetIds || typeof task.targetIds !== 'string') {
142
- logger.warn(`任务配置不完整: targetIds 字段无效或为空, 内容: ${task.content}`);
143
- continue;
144
- }
145
- const targetIds = task.targetIds.split(',').map(id => id.trim()).filter(id => id);
146
- if (targetIds.length === 0) {
147
- logger.warn(`任务目标列表为空: ${task.content},原始值: ${task.targetIds}`);
148
- continue;
149
- }
150
- logger.info(`正在执行广播任务: ${task.content} -> ${targetIds.join(',')} (${task.type})`);
151
- let message = '';
152
- if (task.content === '活跃市值') {
153
- try {
154
- logger.info(`开始获取活跃市值数据...`);
155
- const responseText = await ctx.http.get('http://stock.svip886.com/api/indexes', { responseType: 'text' });
156
- message = `📊 定时广播 - 指数看板:\n\n${responseText}`;
157
- logger.info(`活跃市值数据获取成功, 信息长度: ${message.length}`);
158
- }
159
- catch (apiErr) {
160
- logger.error('获取活跃市值 API 失败', apiErr);
161
- continue;
162
- }
163
- }
164
- else if (task.content === '涨停看板' || task.content === '跌停看板') {
165
- try {
166
- const apiType = task.content === '涨停看板' ? 'limit_up' : 'limit_down';
167
- const imageUrl = `http://stock.svip886.com/api/${apiType}.png`;
168
- logger.info(`开始下载图片: ${imageUrl}`);
169
- const imageBuffer = await ctx.http.get(imageUrl, { responseType: 'arraybuffer' });
170
- logger.info(`图片下载成功, 大小: ${imageBuffer.byteLength} bytes`);
171
- const base64Image = Buffer.from(imageBuffer).toString('base64');
172
- message = `🔔 定时广播 - ${task.content}:\n<img src="data:image/png;base64,${base64Image}" />`;
173
- logger.info(`图片base64编码成功, 信息长度: ${message.length}`);
174
- }
175
- catch (apiErr) {
176
- logger.error(`获取${task.content}图片 API 失败`, apiErr);
177
- continue;
178
- }
179
- }
180
- else {
181
- logger.warn(`不支持的广播内容类型: ${task.content}`);
182
- continue;
183
- }
184
- if (!message) {
185
- logger.warn(`未能生成消息内容: ${task.content}`);
186
- continue;
187
- }
188
- const bot = ctx.bots.find(b => b.status === 'online' || b.status === 1) || ctx.bots[0];
189
- if (!bot) {
190
- logger.error(`无可用机器人实例,总有 ${ctx.bots.length} 个机器人`);
191
- continue;
192
- }
193
- logger.info(`找到机器人: ${bot.platform}, 开始向 ${targetIds.length} 个目标发送`);
194
- for (const targetId of targetIds) {
195
- try {
196
- logger.info(`将发送给 ${targetId}, 类型: ${task.type}`);
197
- if (task.type === 'private') {
198
- await bot.sendPrivateMessage(targetId, message);
199
- }
200
- else {
201
- await bot.sendMessage(targetId, message);
202
- }
203
- logger.info(`广播任务发送成功: ${task.content} -> ${targetId}`);
204
- }
205
- catch (err) {
206
- logger.error(`广播任务发送失败 (${targetId}): ${task.content}`, err);
207
- }
208
- }
209
- }
210
- catch (error) {
211
- logger.error(`定时广播任务执行失败: ${task.content}`, error);
222
+ const success = await performBroadcast(task);
223
+ if (!success) {
224
+ logger.warn(`[任务入队] 任务 ${task.content} 执行失败,加入重试队列`);
225
+ retryQueue.push({
226
+ task,
227
+ originalTime: currentTime,
228
+ retryCount: 0,
229
+ lastAttemptTimestamp: nowTimestamp
230
+ });
212
231
  }
213
232
  }
214
233
  }
@@ -300,7 +319,7 @@ function apply(ctx, config) {
300
319
  ctx.command('活跃市值', '获取活跃市值数据')
301
320
  .action(async ({ session }) => {
302
321
  if (isUserInSpecificBlacklist(session, '活跃市值')) {
303
- return '您已被加入黑名单,无法使用此功能。';
322
+ return;
304
323
  }
305
324
  try {
306
325
  // 使用Koishi的HTTP服务发起请求获取数据
@@ -318,7 +337,7 @@ function apply(ctx, config) {
318
337
  ctx.command('异动 <stockCode:text>', '获取指定股票的异动分析数据')
319
338
  .action(async ({ session }, stockCode) => {
320
339
  if (isUserInSpecificBlacklist(session, '异动')) {
321
- return '您已被加入黑名单,无法使用此功能。';
340
+ return;
322
341
  }
323
342
  if (!stockCode) {
324
343
  return '请输入股票代码,格式:异动 [股票代码]';
@@ -338,7 +357,7 @@ function apply(ctx, config) {
338
357
  ctx.command('涨停看板', '获取涨停看板图片')
339
358
  .action(async ({ session }) => {
340
359
  if (isUserInSpecificBlacklist(session, '涨停看板')) {
341
- return '您已被加入黑名单,无法使用此功能。';
360
+ return;
342
361
  }
343
362
  try {
344
363
  // 使用Koishi的HTTP服务下载图片
@@ -359,7 +378,7 @@ function apply(ctx, config) {
359
378
  ctx.command('跌停看板', '获取跌停看板图片')
360
379
  .action(async ({ session }) => {
361
380
  if (isUserInSpecificBlacklist(session, '跌停看板')) {
362
- return '您已被加入黑名单,无法使用此功能。';
381
+ return;
363
382
  }
364
383
  try {
365
384
  // 使用Koishi的HTTP服务下载图片
@@ -380,7 +399,7 @@ function apply(ctx, config) {
380
399
  ctx.command('选股 <strategy:text>', '根据指定策略选股(支持策略:N型、填坑、少妇、突破、补票、少妇pro)')
381
400
  .action(async ({ session }, strategy) => {
382
401
  if (isUserInSpecificBlacklist(session, '选股')) {
383
- return '您已被加入黑名单,无法使用此功能。';
402
+ return;
384
403
  }
385
404
  if (!strategy) {
386
405
  return '请输入选股策略,格式:选股 [策略名称或编号]\n支持的策略:N型(1)、填坑(2)、少妇(3)、突破(4)、补票(5)、少妇pro(6)';
@@ -426,7 +445,7 @@ function apply(ctx, config) {
426
445
  ctx.command('骑', '获取骑图片')
427
446
  .action(async ({ session }) => {
428
447
  if (isUserInSpecificBlacklist(session, '骑')) {
429
- return '您已被加入黑名单,无法使用此功能。';
448
+ return;
430
449
  }
431
450
  try {
432
451
  // 读取本地图片文件并转换为base64
@@ -451,7 +470,7 @@ function apply(ctx, config) {
451
470
  const content = session.content?.trim();
452
471
  if (content === '活跃市值') {
453
472
  if (isUserInSpecificBlacklist(session, '活跃市值')) {
454
- return '您已被加入黑名单,无法使用此功能。';
473
+ return;
455
474
  }
456
475
  try {
457
476
  // 使用Koishi的HTTP服务发起请求获取数据
@@ -466,7 +485,7 @@ function apply(ctx, config) {
466
485
  }
467
486
  else if (content?.startsWith('异动 ')) {
468
487
  if (isUserInSpecificBlacklist(session, '异动')) {
469
- return '您已被加入黑名单,无法使用此功能。';
488
+ return;
470
489
  }
471
490
  // 解析股票代码
472
491
  const match = content.match(/^异动\s+(.+)$/);
@@ -486,7 +505,7 @@ function apply(ctx, config) {
486
505
  }
487
506
  else if (content === '涨停看板') {
488
507
  if (isUserInSpecificBlacklist(session, '涨停看板')) {
489
- return '您已被加入黑名单,无法使用此功能。';
508
+ return;
490
509
  }
491
510
  try {
492
511
  // 使用Koishi的HTTP服务下载图片
@@ -505,7 +524,7 @@ function apply(ctx, config) {
505
524
  }
506
525
  else if (content === '跌停看板') {
507
526
  if (isUserInSpecificBlacklist(session, '跌停看板')) {
508
- return '您已被加入黑名单,无法使用此功能。';
527
+ return;
509
528
  }
510
529
  try {
511
530
  // 使用Koishi的HTTP服务下载图片
@@ -524,7 +543,7 @@ function apply(ctx, config) {
524
543
  }
525
544
  else if (content?.startsWith('选股 ')) {
526
545
  if (isUserInSpecificBlacklist(session, '选股')) {
527
- return '您已被加入黑名单,无法使用此功能。';
546
+ return;
528
547
  }
529
548
  // 解析选股策略
530
549
  const match = content.match(/^选股\s+(.+)$/);
@@ -570,7 +589,7 @@ function apply(ctx, config) {
570
589
  }
571
590
  else if (content === '骑') {
572
591
  if (isUserInSpecificBlacklist(session, '骑')) {
573
- return '您已被加入黑名单,无法使用此功能。';
592
+ return;
574
593
  }
575
594
  try {
576
595
  // 读取本地图片文件并转换为base64
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-stock",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
4
4
  "description": "A Koishi plugin that fetches stock data and provides market analysis, including active market cap, stock alerts, limit-up board, and stock selection features with configurable blacklists for each command.",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",